summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2025-01-24 20:35:27 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2025-01-24 20:35:27 +0900
commitc80a54e42b52ce297f0f2f71af23c562832025c7 (patch)
treedcce8bb977a770f473325d48f6f70b21d9818a40
init
-rw-r--r--LICENSE674
-rw-r--r--README.md3
-rw-r--r--ar/.config/LazyVim/.gitignore8
-rw-r--r--ar/.config/LazyVim/.neoconf.json15
-rw-r--r--ar/.config/LazyVim/LICENSE201
-rw-r--r--ar/.config/LazyVim/README.md4
-rw-r--r--ar/.config/LazyVim/db_ui/connections.json1
-rw-r--r--ar/.config/LazyVim/init.lua3
-rw-r--r--ar/.config/LazyVim/lua/config/autocmds.lua157
-rw-r--r--ar/.config/LazyVim/lua/config/keymaps.lua275
-rw-r--r--ar/.config/LazyVim/lua/config/lazy.lua105
-rw-r--r--ar/.config/LazyVim/lua/config/options.lua105
-rw-r--r--ar/.config/LazyVim/lua/plugins/better-escape.lua6
-rw-r--r--ar/.config/LazyVim/lua/plugins/bqf.lua9
-rw-r--r--ar/.config/LazyVim/lua/plugins/catppuccin.lua92
-rw-r--r--ar/.config/LazyVim/lua/plugins/cmp.lua50
-rw-r--r--ar/.config/LazyVim/lua/plugins/colorscheme.lua11
-rw-r--r--ar/.config/LazyVim/lua/plugins/comment.lua23
-rw-r--r--ar/.config/LazyVim/lua/plugins/dadbod.lua25
-rw-r--r--ar/.config/LazyVim/lua/plugins/dashboard-nvim.lua68
-rw-r--r--ar/.config/LazyVim/lua/plugins/example.lua265
-rw-r--r--ar/.config/LazyVim/lua/plugins/file-browser.lua15
-rw-r--r--ar/.config/LazyVim/lua/plugins/glow.lua8
-rw-r--r--ar/.config/LazyVim/lua/plugins/goyo.lua53
-rw-r--r--ar/.config/LazyVim/lua/plugins/harpoon.lua19
-rw-r--r--ar/.config/LazyVim/lua/plugins/impatient.lua6
-rw-r--r--ar/.config/LazyVim/lua/plugins/jukit.lua79
-rw-r--r--ar/.config/LazyVim/lua/plugins/jupyter.lua345
-rw-r--r--ar/.config/LazyVim/lua/plugins/lspconfig.lua77
-rw-r--r--ar/.config/LazyVim/lua/plugins/lualine.lua105
-rw-r--r--ar/.config/LazyVim/lua/plugins/magma.lua23
-rw-r--r--ar/.config/LazyVim/lua/plugins/markdown-preview.lua23
-rw-r--r--ar/.config/LazyVim/lua/plugins/mason.lua92
-rw-r--r--ar/.config/LazyVim/lua/plugins/neogen.lua33
-rw-r--r--ar/.config/LazyVim/lua/plugins/notify.lua6
-rw-r--r--ar/.config/LazyVim/lua/plugins/nvim-neo-tree.lua16
-rw-r--r--ar/.config/LazyVim/lua/plugins/obsidian.lua319
-rw-r--r--ar/.config/LazyVim/lua/plugins/oil.lua13
-rw-r--r--ar/.config/LazyVim/lua/plugins/playground.lua38
-rw-r--r--ar/.config/LazyVim/lua/plugins/project.lua6
-rw-r--r--ar/.config/LazyVim/lua/plugins/refactoring.lua35
-rw-r--r--ar/.config/LazyVim/lua/plugins/seoul256.lua3
-rw-r--r--ar/.config/LazyVim/lua/plugins/tagbar.lua12
-rw-r--r--ar/.config/LazyVim/lua/plugins/telescope.lua24
-rw-r--r--ar/.config/LazyVim/lua/plugins/tmux-navigator.lua19
-rw-r--r--ar/.config/LazyVim/lua/plugins/tokyonight.lua10
-rw-r--r--ar/.config/LazyVim/lua/plugins/treesj.lua32
-rw-r--r--ar/.config/LazyVim/lua/plugins/vimwiki.lua37
-rw-r--r--ar/.config/LazyVim/lua/plugins/which-key.lua55
-rw-r--r--ar/.config/LazyVim/lua/plugins/yanky.lua31
-rw-r--r--ar/.config/LazyVim/lua/plugins/zen-mode.lua11
-rw-r--r--ar/.config/LazyVim/stylua.toml3
-rw-r--r--ar/.config/LazyVim/vscode/easymotion-config.vim2
-rw-r--r--ar/.config/LazyVim/vscode/plugins.lua153
-rw-r--r--ar/.config/LazyVim/vscode/remap.lua32
-rw-r--r--ar/.config/LazyVim/vscode/settings.vim49
-rw-r--r--ar/.config/NvChad/.ignore1
-rw-r--r--ar/.config/NvChad/.stylua.toml6
-rw-r--r--ar/.config/NvChad/LICENSE674
-rw-r--r--ar/.config/NvChad/db_ui/connections.json1
-rw-r--r--ar/.config/NvChad/db_ui/si/safTEST1
-rw-r--r--ar/.config/NvChad/init.lua21
-rw-r--r--ar/.config/NvChad/lua/core/bootstrap.lua62
-rw-r--r--ar/.config/NvChad/lua/core/default_config.lua92
-rw-r--r--ar/.config/NvChad/lua/core/init.lua116
-rw-r--r--ar/.config/NvChad/lua/core/mappings.lua468
-rw-r--r--ar/.config/NvChad/lua/core/utils.lua118
-rw-r--r--ar/.config/NvChad/lua/custom/README.md3
-rw-r--r--ar/.config/NvChad/lua/custom/chadrc.lua54
-rw-r--r--ar/.config/NvChad/lua/custom/configs/cell_marker.lua159
-rw-r--r--ar/.config/NvChad/lua/custom/configs/dadbod.lua29
-rw-r--r--ar/.config/NvChad/lua/custom/configs/lspconfig.lua71
-rw-r--r--ar/.config/NvChad/lua/custom/configs/null-ls.lua45
-rw-r--r--ar/.config/NvChad/lua/custom/configs/overrides.lua247
-rw-r--r--ar/.config/NvChad/lua/custom/highlights.lua21
-rw-r--r--ar/.config/NvChad/lua/custom/init.lua55
-rw-r--r--ar/.config/NvChad/lua/custom/mappings.lua740
-rw-r--r--ar/.config/NvChad/lua/custom/plugins.lua862
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/cmp.lua120
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/lazy_nvim.lua47
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/lspconfig.lua67
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/mason.lua28
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/nvimtree.lua77
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/others.lua66
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/telescope.lua63
-rw-r--r--ar/.config/NvChad/lua/plugins/configs/treesitter.lua12
-rw-r--r--ar/.config/NvChad/lua/plugins/init.lua273
-rw-r--r--ar/.config/NvChad/vim_cheatsheet.webpbin0 -> 671056 bytes
-rw-r--r--ar/.config/NvChad/vim_cheatsheet2.webpbin0 -> 283702 bytes
-rw-r--r--ar/.config/NvChad/vscode/easymotion-config.vim2
-rw-r--r--ar/.config/NvChad/vscode/plugins.lua153
-rw-r--r--ar/.config/NvChad/vscode/remap.lua32
-rw-r--r--ar/.config/NvChad/vscode/settings.vim49
-rw-r--r--ar/.config/TheSiahxyz/after/queries/markdown/textobjects.scm3
-rw-r--r--ar/.config/TheSiahxyz/ftplugin/markdown.lua440
-rw-r--r--ar/.config/TheSiahxyz/ftplugin/quarto.lua6
-rw-r--r--ar/.config/TheSiahxyz/init.lua1
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/core/autocmds.lua381
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/core/keymaps.lua806
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/core/lazy.lua17
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/core/options.lua42
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/health.lua45
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/init.lua15
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ai.lua286
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cloak.lua42
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cmp.lua326
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/colorschemes.lua268
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/comment.lua50
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/compiler.lua53
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/context.lua26
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/csv.lua88
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dadbod.lua52
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dap.lua295
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/docker.lua222
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/git.lua221
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/goyo.lua39
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/harpoon2.lua103
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/image.lua210
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/init.lua83
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/keys.lua278
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lf.lua121
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lsp.lua643
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lualine.lua259
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/markdown.lua454
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/marks.lua16
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/mini.lua1132
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/navic.lua52
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/obsidian.lua616
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/outline.lua11
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/project.lua12
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/python.lua59
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/quickfix.lua76
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/refactoring.lua60
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/sessions.lua32
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/silicon.lua145
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/snippets.lua72
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/surround.lua14
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/telescope.lua1041
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/textobject.lua140
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/treesitter.lua62
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ufo.lua56
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/urlview.lua103
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/vimwiki.lua64
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/zenmode.lua46
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/markdown.lua187
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode1.lua103
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode2.lua104
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/quarto.lua28
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/whichkey.lua18
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add113
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add.splbin0 -> 1365 bytes
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet.lua13
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/grid.lua105
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/init.lua116
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/icons.lua168
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/markdown.lua26
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/snippet.lua38
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tasks.lua227
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tmux.lua63
-rw-r--r--ar/.config/TheSiahxyz/lua/thesiahxyz/utils/utils.lua112
-rw-r--r--ar/.config/asus_jack_audio_issue.txt7
-rw-r--r--ar/.config/atuin/config.toml210
-rw-r--r--ar/.config/auto-cpufreq/auto-cpufreq.conf61
-rw-r--r--ar/.config/bash/bash_profile40
-rw-r--r--ar/.config/bash/bashrc703
-rw-r--r--ar/.config/bat/config8
-rw-r--r--ar/.config/bat/themes/Catppuccin Frappe.tmTheme2059
-rw-r--r--ar/.config/bat/themes/Catppuccin Latte.tmTheme2059
-rw-r--r--ar/.config/bat/themes/Catppuccin Macchiato.tmTheme2059
-rw-r--r--ar/.config/bat/themes/Catppuccin Mocha.tmTheme2059
-rw-r--r--ar/.config/blacklist.conf1
-rw-r--r--ar/.config/calcurse/conf33
-rw-r--r--ar/.config/calcurse/keys56
-rw-r--r--ar/.config/crons14
-rw-r--r--ar/.config/dunst/dunstrc76
-rw-r--r--ar/.config/fastfetch/config.jsonc167
-rw-r--r--ar/.config/fcitx5/conf/cached_layouts3670
-rw-r--r--ar/.config/fcitx5/conf/clipboard.conf7
-rw-r--r--ar/.config/fcitx5/conf/hangul.conf19
-rw-r--r--ar/.config/fcitx5/conf/keyboard.conf27
-rw-r--r--ar/.config/fcitx5/conf/notifications.conf3
-rw-r--r--ar/.config/fcitx5/conf/quickphrase.conf9
-rw-r--r--ar/.config/fcitx5/conf/waylandim.conf5
-rw-r--r--ar/.config/fcitx5/conf/xcb.conf5
-rw-r--r--ar/.config/fcitx5/conf/xim.conf3
-rw-r--r--ar/.config/fcitx5/config79
-rw-r--r--ar/.config/fcitx5/profile23
-rw-r--r--ar/.config/firefox/chrome/userChrome.css106
-rw-r--r--ar/.config/firefox/enhanceforyoutube.json109
-rw-r--r--ar/.config/firefox/thesiah.js79
-rw-r--r--ar/.config/firefox/vdh-settings.json90
-rw-r--r--ar/.config/firefox/vimium-options.json25
-rw-r--r--ar/.config/fontconfig/fonts.conf70
-rw-r--r--ar/.config/gem/gemrc3
-rw-r--r--ar/.config/git/attributes98
-rw-r--r--ar/.config/git/config81
-rw-r--r--ar/.config/git/gitk64
-rw-r--r--ar/.config/git/ignore112
-rw-r--r--ar/.config/gitmux/gitmux.conf86
-rw-r--r--ar/.config/gtk-2.0/gtkrc-2.021
-rw-r--r--ar/.config/gtk-3.0/settings.ini20
-rw-r--r--ar/.config/lazygit/config.yml15
-rwxr-xr-xar/.config/lf/cleaner4
-rw-r--r--ar/.config/lf/icons305
-rw-r--r--ar/.config/lf/lfrc607
-rw-r--r--ar/.config/lf/rooticons336
-rwxr-xr-xar/.config/lf/scope83
-rw-r--r--ar/.config/mimeapps.list26
-rw-r--r--ar/.config/mpd/mpd.conf19
-rw-r--r--ar/.config/mpd/playlists/entire.m3u890
-rw-r--r--ar/.config/mpd/playlists/jpop.m3u156
-rw-r--r--ar/.config/mpd/playlists/kpop.m3u113
-rw-r--r--ar/.config/mpd/playlists/pop.m3u56
-rw-r--r--ar/.config/mpv/input.conf114
-rw-r--r--ar/.config/mpv/lua_settings/blur_edges.conf26
-rw-r--r--ar/.config/mpv/lua_settings/gallery_worker.conf18
-rw-r--r--ar/.config/mpv/lua_settings/mpv_crop_script.conf2
-rw-r--r--ar/.config/mpv/lua_settings/mpv_thumbnail_script.conf3
-rw-r--r--ar/.config/mpv/lua_settings/playlist_view.conf123
-rw-r--r--ar/.config/mpv/mpv.conf11
-rw-r--r--ar/.config/mpv/osc.conf1
-rw-r--r--ar/.config/mpv/script-modules/extended-menu.lua1214
-rw-r--r--ar/.config/mpv/script-modules/gallery.lua581
-rw-r--r--ar/.config/mpv/script-modules/input-console.lua935
-rw-r--r--ar/.config/mpv/script-modules/mpvSockets.lua36
-rw-r--r--ar/.config/mpv/script-modules/scroll-list.lua293
-rw-r--r--ar/.config/mpv/script-modules/sha1.lua334
-rw-r--r--ar/.config/mpv/script-modules/user-input-module.lua126
-rw-r--r--ar/.config/mpv/script-modules/utf8/LICENSE21
-rw-r--r--ar/.config/mpv/script-modules/utf8/README.md93
-rw-r--r--ar/.config/mpv/script-modules/utf8/begins/compiletime/parser.lua17
-rw-r--r--ar/.config/mpv/script-modules/utf8/begins/compiletime/vanilla.lua60
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/compiletime/builder.lua128
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/compiletime/parser.lua21
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/compiletime/range.lua44
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/compiletime/stub.lua9
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/compiletime/vanilla.lua131
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/runtime/base.lua184
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/runtime/dummy.lua41
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/runtime/init.lua22
-rw-r--r--ar/.config/mpv/script-modules/utf8/charclass/runtime/native.lua47
-rw-r--r--ar/.config/mpv/script-modules/utf8/context/compiletime.lua18
-rw-r--r--ar/.config/mpv/script-modules/utf8/context/runtime.lua112
-rw-r--r--ar/.config/mpv/script-modules/utf8/ends/compiletime/parser.lua17
-rw-r--r--ar/.config/mpv/script-modules/utf8/ends/compiletime/vanilla.lua46
-rw-r--r--ar/.config/mpv/script-modules/utf8/functions/lua53.lua152
-rw-r--r--ar/.config/mpv/script-modules/utf8/init.lua71
-rw-r--r--ar/.config/mpv/script-modules/utf8/modifier/compiletime/frontier.lua50
-rw-r--r--ar/.config/mpv/script-modules/utf8/modifier/compiletime/parser.lua20
-rw-r--r--ar/.config/mpv/script-modules/utf8/modifier/compiletime/simple.lua23
-rw-r--r--ar/.config/mpv/script-modules/utf8/modifier/compiletime/stub.lua28
-rw-r--r--ar/.config/mpv/script-modules/utf8/modifier/compiletime/vanilla.lua270
-rw-r--r--ar/.config/mpv/script-modules/utf8/primitives/dummy.lua555
-rw-r--r--ar/.config/mpv/script-modules/utf8/primitives/init.lua23
-rw-r--r--ar/.config/mpv/script-modules/utf8/primitives/native.lua57
-rw-r--r--ar/.config/mpv/script-modules/utf8/primitives/tarantool.lua13
-rw-r--r--ar/.config/mpv/script-modules/utf8/regex_parser.lua80
-rwxr-xr-xar/.config/mpv/script-modules/utf8/test.sh23
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/charclass_compiletime.lua165
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/charclass_runtime.lua116
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/context_runtime.lua82
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/strict.lua42
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/test.lua205
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/test_compat.lua109
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/test_pm.lua392
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/test_utf8data.lua15
-rw-r--r--ar/.config/mpv/script-modules/utf8/test/util.lua75
-rw-r--r--ar/.config/mpv/script-modules/utf8/util.lua64
-rw-r--r--ar/.config/mpv/script-modules/utf8_data.lua1865
-rw-r--r--ar/.config/mpv/script-opts/SimpleBookmark.conf311
-rw-r--r--ar/.config/mpv/script-opts/SmartCopyPaste_II.conf343
-rw-r--r--ar/.config/mpv/script-opts/SmartSkip.conf221
-rw-r--r--ar/.config/mpv/script-opts/command_palette.conf19
-rw-r--r--ar/.config/mpv/script-opts/mdmenu.conf13
-rw-r--r--ar/.config/mpv/script-opts/playlist_view.conf123
-rw-r--r--ar/.config/mpv/script-opts/thumbfast.conf41
-rw-r--r--ar/.config/mpv/scripts/Rename.lua99
-rw-r--r--ar/.config/mpv/scripts/SimpleBookmark.lua2907
-rw-r--r--ar/.config/mpv/scripts/SmartCopyPaste_II.lua3797
-rw-r--r--ar/.config/mpv/scripts/SmartSkip.lua1936
-rw-r--r--ar/.config/mpv/scripts/UndoRedo.lua212
-rw-r--r--ar/.config/mpv/scripts/blackout.lua73
-rw-r--r--ar/.config/mpv/scripts/blur-edges.lua174
-rw-r--r--ar/.config/mpv/scripts/change-OSD-media-title.lua36
-rw-r--r--ar/.config/mpv/scripts/command_palette.lua1229
-rw-r--r--ar/.config/mpv/scripts/cycle-video-rotate.lua36
-rw-r--r--ar/.config/mpv/scripts/delete_current_file.lua168
-rw-r--r--ar/.config/mpv/scripts/fuzzydir.lua278
-rw-r--r--ar/.config/mpv/scripts/gallery-thumbgen.lua342
-rw-r--r--ar/.config/mpv/scripts/history.lua138
-rw-r--r--ar/.config/mpv/scripts/mdmenu.lua285
-rw-r--r--ar/.config/mpv/scripts/misc.lua594
-rw-r--r--ar/.config/mpv/scripts/modules.lua5
-rw-r--r--ar/.config/mpv/scripts/mpv_crop_script.lua3173
-rw-r--r--ar/.config/mpv/scripts/navigator.lua603
-rw-r--r--ar/.config/mpv/scripts/osc-show-hide.lua40
-rw-r--r--ar/.config/mpv/scripts/osc.lua3109
-rw-r--r--ar/.config/mpv/scripts/playlist-view.lua925
-rw-r--r--ar/.config/mpv/scripts/playlistmanager.lua1755
-rw-r--r--ar/.config/mpv/scripts/reload.lua19
-rw-r--r--ar/.config/mpv/scripts/sponsorblock_minimal.lua141
-rw-r--r--ar/.config/mpv/scripts/subtitle-search.lua682
-rw-r--r--ar/.config/mpv/scripts/thumbfast.lua951
-rw-r--r--ar/.config/mpv/scripts/user-input.lua890
-rw-r--r--ar/.config/mpv/scripts/xscreensaver.lua24
-rw-r--r--ar/.config/mpv/scripts/youtube-search.lua419
-rw-r--r--ar/.config/mpv/scripts/ytdl-preload.lua433
-rw-r--r--ar/.config/mpv/unused_scipts/xrandr.lua382
-rw-r--r--ar/.config/mutt/README.md414
-rw-r--r--ar/.config/ncmpcpp/bindings468
-rw-r--r--ar/.config/ncmpcpp/config261
-rw-r--r--ar/.config/newsboat/config60
-rw-r--r--ar/.config/newsboat/urls27
-rw-r--r--ar/.config/npm/.npmrc2
-rwxr-xr-xar/.config/nsxiv/exec/key-handler67
-rw-r--r--ar/.config/nvidia.hook15
-rw-r--r--ar/.config/openvpn/thesiah.ovpn78
-rw-r--r--ar/.config/pam-gnupg2
-rwxr-xr-xar/.config/pinentry/preexec5
-rw-r--r--ar/.config/pip/pip.conf2
-rw-r--r--ar/.config/pipewire/pipewire.conf.d/user-session.conf4
-rw-r--r--ar/.config/profanity/profrc94
-rw-r--r--ar/.config/pulse/daemon.conf4
-rw-r--r--ar/.config/python/pythonrc2
-rw-r--r--ar/.config/sesh/sesh.toml43
-rw-r--r--ar/.config/shell/aliasrc467
-rw-r--r--ar/.config/shell/bm-dirs108
-rw-r--r--ar/.config/shell/bm-files51
-rw-r--r--ar/.config/shell/git-aliasrc415
-rw-r--r--ar/.config/shell/inputrc31
-rw-r--r--ar/.config/shell/profile231
-rw-r--r--ar/.config/starship/starship.toml204
-rw-r--r--ar/.config/stig/rc1
-rw-r--r--ar/.config/stig/themerc600
-rw-r--r--ar/.config/task/taskrc191
-rw-r--r--ar/.config/taskopen/taskopenrc30
-rw-r--r--ar/.config/tmux/tmux.conf303
-rw-r--r--ar/.config/transmission-daemon/settings.json83
-rw-r--r--ar/.config/user-dirs.dirs11
-rw-r--r--ar/.config/vim/UltiSnips/all.snippets30
-rw-r--r--ar/.config/vim/init.vim430
-rw-r--r--ar/.config/vim/plugins.vim42
-rw-r--r--ar/.config/vim/vimrc586
-rw-r--r--ar/.config/vscode/argv.json21
-rwxr-xr-xar/.config/wal/postrun33
-rw-r--r--ar/.config/wal/templates/dunstrc27
-rw-r--r--ar/.config/wal/templates/zathurarc46
-rw-r--r--ar/.config/wget/wgetrc1
-rw-r--r--ar/.config/x11/xinitrc19
-rw-r--r--ar/.config/x11/xprofile49
-rw-r--r--ar/.config/x11/xresources386
-rw-r--r--ar/.config/youtube-viewer/youtube-viewer.conf141
-rw-r--r--ar/.config/zsh/.zshrc114
-rw-r--r--ar/.config/zsh/autocomplete.zsh70
-rw-r--r--ar/.config/zsh/git.zsh7
-rw-r--r--ar/.config/zsh/keymaps.zsh342
-rw-r--r--ar/.config/zsh/p10k.zsh1721
-rw-r--r--ar/.config/zsh/packages.zsh22
-rw-r--r--ar/.config/zsh/plugins.zsh101
-rw-r--r--ar/.config/zsh/scripts.zsh836
-rw-r--r--ar/.gitmodules3
-rwxr-xr-xar/.local/bin/arkenfox-auto-update26
-rwxr-xr-xar/.local/bin/bash-preexec380
-rwxr-xr-xar/.local/bin/bookmarks164
-rwxr-xr-xar/.local/bin/booksplit45
-rwxr-xr-xar/.local/bin/browse85
-rwxr-xr-xar/.local/bin/browserprofile102
-rwxr-xr-xar/.local/bin/clonerepo61
-rwxr-xr-xar/.local/bin/compiler104
-rwxr-xr-xar/.local/bin/concatvideo56
-rwxr-xr-xar/.local/bin/createsh104
-rw-r--r--ar/.local/bin/cron/README.md11
-rwxr-xr-xar/.local/bin/cron/checkup16
-rwxr-xr-xar/.local/bin/cron/crontog26
-rwxr-xr-xar/.local/bin/cron/mediaup34
-rwxr-xr-xar/.local/bin/cron/newsup15
-rwxr-xr-xar/.local/bin/cutvideo41
-rwxr-xr-xar/.local/bin/displayselect92
-rwxr-xr-xar/.local/bin/dmenubrowse44
-rwxr-xr-xar/.local/bin/dmenudelmusic44
-rwxr-xr-xar/.local/bin/dmenuhandler24
-rwxr-xr-xar/.local/bin/dmenuman5
-rwxr-xr-xar/.local/bin/dmenumountcifs19
-rwxr-xr-xar/.local/bin/dmenupass6
-rwxr-xr-xar/.local/bin/dmenurecord129
-rwxr-xr-xar/.local/bin/dmenusmbadd58
-rwxr-xr-xar/.local/bin/dmenusmbdel55
-rwxr-xr-xar/.local/bin/dmenuunicode18
-rwxr-xr-xar/.local/bin/dmenuupgrade115
-rwxr-xr-xar/.local/bin/dvdburn44
-rwxr-xr-xar/.local/bin/ecrypt35
-rwxr-xr-xar/.local/bin/emojiupdate65
-rwxr-xr-xar/.local/bin/ethwifi16
-rwxr-xr-xar/.local/bin/extractkeys142
-rwxr-xr-xar/.local/bin/fzffiles45
-rwxr-xr-xar/.local/bin/fzffns74
-rwxr-xr-xar/.local/bin/fzfpass89
-rwxr-xr-xar/.local/bin/getbib14
-rwxr-xr-xar/.local/bin/getcomproot9
-rwxr-xr-xar/.local/bin/getkeys6
-rwxr-xr-xar/.local/bin/gitfiles72
-rwxr-xr-xar/.local/bin/gitopenbranch24
-rwxr-xr-xar/.local/bin/gitstagedfiles11
-rwxr-xr-xar/.local/bin/gitupdate91
-rwxr-xr-xar/.local/bin/gpt26
-rwxr-xr-xar/.local/bin/gracefulkill25
-rwxr-xr-xar/.local/bin/ifinstalled12
-rwxr-xr-xar/.local/bin/iwaf96
-rwxr-xr-xar/.local/bin/lastnvim77
-rwxr-xr-xar/.local/bin/lfub24
-rwxr-xr-xar/.local/bin/linkhandler31
-rwxr-xr-xar/.local/bin/maimpick23
-rwxr-xr-xar/.local/bin/mbackup14
-rwxr-xr-xar/.local/bin/monitorbright24
-rwxr-xr-xar/.local/bin/mounter181
-rwxr-xr-xar/.local/bin/mpdmenu111
-rwxr-xr-xar/.local/bin/mpvplay189
-rwxr-xr-xar/.local/bin/noisereduce81
-rwxr-xr-xar/.local/bin/openfiles34
-rwxr-xr-xar/.local/bin/opensessions37
-rwxr-xr-xar/.local/bin/opentasktui27
-rwxr-xr-xar/.local/bin/opout16
-rwxr-xr-xar/.local/bin/otp54
-rwxr-xr-xar/.local/bin/ovpn22
-rwxr-xr-xar/.local/bin/pacerror27
-rwxr-xr-xar/.local/bin/partlabel85
-rwxr-xr-xar/.local/bin/passmenu238
-rwxr-xr-xar/.local/bin/pauseallmpv10
-rwxr-xr-xar/.local/bin/peertubetorrent9
-rwxr-xr-xar/.local/bin/podentr7
-rwxr-xr-xar/.local/bin/qndl108
-rwxr-xr-xar/.local/bin/queueandnotify14
-rwxr-xr-xar/.local/bin/rbackup185
-rwxr-xr-xar/.local/bin/remapd8
-rwxr-xr-xar/.local/bin/remaps66
-rwxr-xr-xar/.local/bin/restartnvim25
-rwxr-xr-xar/.local/bin/rgafiles120
-rwxr-xr-xar/.local/bin/rotdir12
-rwxr-xr-xar/.local/bin/rssadd18
-rwxr-xr-xar/.local/bin/schedule22
-rwxr-xr-xar/.local/bin/screenshotactivewindow37
-rwxr-xr-xar/.local/bin/sd22
-rwxr-xr-xar/.local/bin/sessionizer36
-rwxr-xr-xar/.local/bin/setbg71
-rwxr-xr-xar/.local/bin/setlock23
-rwxr-xr-xar/.local/bin/setmonitor35
-rwxr-xr-xar/.local/bin/shortcuts86
-rwxr-xr-xar/.local/bin/slider132
-rwxr-xr-xar/.local/bin/sshadd31
-rwxr-xr-xar/.local/bin/statusbar/sb-battery68
-rwxr-xr-xar/.local/bin/statusbar/sb-bghitness19
-rwxr-xr-xar/.local/bin/statusbar/sb-brightness11
-rwxr-xr-xar/.local/bin/statusbar/sb-clock77
-rwxr-xr-xar/.local/bin/statusbar/sb-cpu12
-rwxr-xr-xar/.local/bin/statusbar/sb-cpubars44
-rwxr-xr-xar/.local/bin/statusbar/sb-disk27
-rwxr-xr-xar/.local/bin/statusbar/sb-ecrypt12
-rwxr-xr-xar/.local/bin/statusbar/sb-forecast402
-rwxr-xr-xar/.local/bin/statusbar/sb-help-icon22
-rwxr-xr-xar/.local/bin/statusbar/sb-internet42
-rwxr-xr-xar/.local/bin/statusbar/sb-iplocate15
-rwxr-xr-xar/.local/bin/statusbar/sb-keyboard35
-rwxr-xr-xar/.local/bin/statusbar/sb-mailbox23
-rwxr-xr-xar/.local/bin/statusbar/sb-memory18
-rwxr-xr-xar/.local/bin/statusbar/sb-mpdup8
-rwxr-xr-xar/.local/bin/statusbar/sb-music78
-rwxr-xr-xar/.local/bin/statusbar/sb-nettraf29
-rwxr-xr-xar/.local/bin/statusbar/sb-news17
-rwxr-xr-xar/.local/bin/statusbar/sb-packages37
-rwxr-xr-xar/.local/bin/statusbar/sb-popupgrade9
-rwxr-xr-xar/.local/bin/statusbar/sb-price67
-rwxr-xr-xar/.local/bin/statusbar/sb-queues40
-rwxr-xr-xar/.local/bin/statusbar/sb-repos130
-rwxr-xr-xar/.local/bin/statusbar/sb-tasks84
-rwxr-xr-xar/.local/bin/statusbar/sb-torrent33
-rwxr-xr-xar/.local/bin/statusbar/sb-volume43
-rwxr-xr-xar/.local/bin/stw87
-rwxr-xr-xar/.local/bin/syncdot46
-rwxr-xr-xar/.local/bin/synctime22
-rwxr-xr-xar/.local/bin/sysact26
-rwxr-xr-xar/.local/bin/tablet39
-rwxr-xr-xar/.local/bin/tag49
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/annotate-with-new-note41
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/annotate-with-note41
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/cycle-priority35
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/cycle-tmux-projects31
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/decrease-priority15
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/git-issue-sync185
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/increase-priority60
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/lib-task-interop82
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/task-switch-context23
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/taskopen-annotation11
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/taskopen-line51
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/tasks-sync11
-rwxr-xr-xar/.local/bin/task/taskwarrior-tui/toggle-review-label13
-rwxr-xr-xar/.local/bin/td-toggle11
-rwxr-xr-xar/.local/bin/texclear8
-rwxr-xr-xar/.local/bin/timer14
-rwxr-xr-xar/.local/bin/timer_14
-rwxr-xr-xar/.local/bin/timezones21
-rwxr-xr-xar/.local/bin/tmuxcreate42
-rwxr-xr-xar/.local/bin/tmuxopen193
-rwxr-xr-xar/.local/bin/tmuxtogglebar6
-rwxr-xr-xar/.local/bin/tmuxtoggleterm11
-rwxr-xr-xar/.local/bin/tordone10
-rwxr-xr-xar/.local/bin/torwrap8
-rwxr-xr-xar/.local/bin/transadd29
-rwxr-xr-xar/.local/bin/tutorialvids26
-rwxr-xr-xar/.local/bin/unewsboat16
-rwxr-xr-xar/.local/bin/unix26
-rwxr-xr-xar/.local/bin/unmounter44
-rwxr-xr-xar/.local/bin/vipy42
-rwxr-xr-xar/.local/bin/wallset295
-rwxr-xr-xar/.local/bin/weath46
-rwxr-xr-xar/.local/bin/whereami24
-rwxr-xr-xar/.local/bin/xdg-terminal-exec3
-rwxr-xr-xar/.local/bin/xinputconf39
-rw-r--r--ar/.lynxrc344
-rw-r--r--ar/.stow-local-ignore34
-rw-r--r--default/.gnupg/gpg-agent.conf3
-rw-r--r--default/.gnupg/sshcontrol12
-rw-r--r--default/.local/share/applications/csv.desktop4
-rw-r--r--default/.local/share/applications/file.desktop4
-rw-r--r--default/.local/share/applications/html.desktop4
-rw-r--r--default/.local/share/applications/img.desktop4
-rw-r--r--default/.local/share/applications/mail.desktop4
-rw-r--r--default/.local/share/applications/office.desktop4
-rw-r--r--default/.local/share/applications/pdf.desktop4
-rw-r--r--default/.local/share/applications/roff.desktop4
-rw-r--r--default/.local/share/applications/rss.desktop4
-rw-r--r--default/.local/share/applications/slide.desktop4
-rw-r--r--default/.local/share/applications/text.desktop4
-rw-r--r--default/.local/share/applications/torrent.desktop4
-rw-r--r--default/.local/share/applications/video.desktop4
-rw-r--r--default/.local/share/thesiah/chars/emoji1667
-rw-r--r--default/.local/share/thesiah/chars/font-awesome1456
-rw-r--r--default/.local/share/thesiah/icons/TheSiahxyz-git.pngbin0 -> 5661 bytes
-rw-r--r--default/.local/share/thesiah/icons/TheSiahxyz.pngbin0 -> 69009 bytes
-rw-r--r--default/.local/share/thesiah/icons/TheSiahxyz.webpbin0 -> 62382 bytes
-rw-r--r--default/.local/share/thesiah/icons/TheSiahxyz.xcfbin0 -> 254098 bytes
-rw-r--r--default/.local/share/thesiah/keys/calcurse10
-rw-r--r--default/.local/share/thesiah/keys/mutt34
-rw-r--r--default/.local/share/thesiah/keys/ncmpcpp21
-rw-r--r--default/.local/share/thesiah/keys/newsboat22
-rw-r--r--default/.local/share/thesiah/keys/nsxiv16
-rw-r--r--default/.local/share/thesiah/keys/zathura19
-rw-r--r--default/.local/share/thesiah/snippets6
-rw-r--r--default/.local/share/thesiah/ttymaps.kmap3
-rw-r--r--default/.local/share/venvs/default-requirements.txt25
-rw-r--r--default/.ssh/config1
-rw-r--r--default/Music/.music.txt899
-rw-r--r--default/Pictures/resources/hhkb-fn-layout.webpbin0 -> 20134 bytes
-rw-r--r--default/Pictures/resources/hhkb-layout.pngbin0 -> 18488 bytes
-rw-r--r--default/Pictures/resources/task-2.3.0.pngbin0 -> 1096408 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet1-ko.webpbin0 -> 283702 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet1.jpgbin0 -> 168820 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet2-ko.webpbin0 -> 671056 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet2.jpgbin0 -> 275342 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet3.jpgbin0 -> 702569 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet4.jpgbin0 -> 100928 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet5.jpgbin0 -> 310739 bytes
-rw-r--r--default/Pictures/resources/vim-cheatsheet6.jpgbin0 -> 281947 bytes
-rw-r--r--default/Pictures/wallpaper/alley.jpgbin0 -> 1674770 bytes
-rw-r--r--default/Pictures/wallpaper/ani-girl.pngbin0 -> 6043132 bytes
-rw-r--r--default/Pictures/wallpaper/aurora-sky.jpgbin0 -> 4918080 bytes
-rw-r--r--default/Pictures/wallpaper/aurora-tree.jpgbin0 -> 1252914 bytes
-rw-r--r--default/Pictures/wallpaper/autumn-paint.jpgbin0 -> 339205 bytes
-rw-r--r--default/Pictures/wallpaper/ball.jpgbin0 -> 16535730 bytes
-rw-r--r--default/Pictures/wallpaper/bedroom.jpgbin0 -> 1765863 bytes
-rw-r--r--default/Pictures/wallpaper/city.pngbin0 -> 39087969 bytes
-rw-r--r--default/Pictures/wallpaper/dark-ghost.jpgbin0 -> 1044029 bytes
-rw-r--r--default/Pictures/wallpaper/dark-sky.pngbin0 -> 2254237 bytes
-rw-r--r--default/Pictures/wallpaper/flower.jpgbin0 -> 7552302 bytes
-rw-r--r--default/Pictures/wallpaper/forest-road.jpgbin0 -> 273435 bytes
-rw-r--r--default/Pictures/wallpaper/horse.jpgbin0 -> 16398633 bytes
-rw-r--r--default/Pictures/wallpaper/moon-girl.jpgbin0 -> 3884301 bytes
-rw-r--r--default/Pictures/wallpaper/moon-space.jpgbin0 -> 591360 bytes
-rw-r--r--default/Pictures/wallpaper/mountain.pngbin0 -> 4496093 bytes
-rw-r--r--default/Pictures/wallpaper/night-river.jpgbin0 -> 2633695 bytes
-rw-r--r--default/Pictures/wallpaper/sky.jpgbin0 -> 1567880 bytes
-rw-r--r--default/Pictures/wallpaper/snow-bubble.jpgbin0 -> 4744061 bytes
-rw-r--r--default/Pictures/wallpaper/snow-dogs.jpgbin0 -> 1763058 bytes
-rw-r--r--default/Pictures/wallpaper/snow-lake.jpgbin0 -> 8291108 bytes
-rw-r--r--default/Pictures/wallpaper/snow-load.jpgbin0 -> 2352700 bytes
-rw-r--r--default/Pictures/wallpaper/snow-tree.jpgbin0 -> 2056433 bytes
-rw-r--r--default/Pictures/wallpaper/snow-tree2.jpgbin0 -> 12502250 bytes
-rw-r--r--default/Pictures/wallpaper/snow-tree3.jpgbin0 -> 2398939 bytes
-rw-r--r--default/Pictures/wallpaper/space.pngbin0 -> 34482414 bytes
-rw-r--r--default/Pictures/wallpaper/sunset-city.jpgbin0 -> 1841593 bytes
-rw-r--r--default/Pictures/wallpaper/sunset-tree.jpgbin0 -> 3180646 bytes
-rw-r--r--default/Pictures/wallpaper/surfing.jpgbin0 -> 3073623 bytes
-rw-r--r--default/Pictures/wallpaper/woman.jpgbin0 -> 1329496 bytes
-rw-r--r--mac/.config/borders/bordersrc12
-rw-r--r--mac/.config/karabiner/assets/complex_modifications/1700711318.json95
-rw-r--r--mac/.config/karabiner/assets/complex_modifications/1700983079.json62
-rw-r--r--mac/.config/karabiner/assets/complex_modifications/1704627767.json20
-rw-r--r--mac/.config/karabiner/assets/complex_modifications/1704850809.json25
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20231122.json200
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20231126.json393
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20231128.json464
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240107.json393
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240108.json417
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240109.json417
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240112.json461
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240113.json472
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240114.json391
-rw-r--r--mac/.config/karabiner/automatic_backups/karabiner_20240115.json391
-rw-r--r--mac/.config/karabiner/karabiner.json290
-rw-r--r--mac/.config/kitty/icons/kitty-dark.icnsbin0 -> 704703 bytes
-rw-r--r--mac/.config/kitty/icons/kitty-light.icnsbin0 -> 544612 bytes
-rw-r--r--mac/.config/kitty/icons/neue_azure.icnsbin0 -> 1339049 bytes
-rw-r--r--mac/.config/kitty/icons/neue_ember.icnsbin0 -> 1309706 bytes
-rw-r--r--mac/.config/kitty/icons/neue_outrun.icnsbin0 -> 1341772 bytes
-rw-r--r--mac/.config/kitty/icons/neue_toxic.icnsbin0 -> 1336970 bytes
-rw-r--r--mac/.config/kitty/icons/outrun.icnsbin0 -> 1132143 bytes
-rw-r--r--mac/.config/kitty/kitty.conf145
-rw-r--r--mac/.config/neofetch/config.conf863
-rw-r--r--mac/.config/pam-gnupg2
-rw-r--r--mac/.config/sketchybar/colors.sh104
-rw-r--r--mac/.config/sketchybar/globalstyles.sh132
-rw-r--r--mac/.config/sketchybar/helper/cpu.h122
-rw-r--r--mac/.config/sketchybar/helper/helperbin0 -> 34872 bytes
-rw-r--r--mac/.config/sketchybar/helper/helper.c31
-rw-r--r--mac/.config/sketchybar/helper/makefile3
-rw-r--r--mac/.config/sketchybar/helper/sketchybar.h209
-rw-r--r--mac/.config/sketchybar/icons.sh70
-rw-r--r--mac/.config/sketchybar/items/apple.sh44
-rw-r--r--mac/.config/sketchybar/items/battery.sh23
-rw-r--r--mac/.config/sketchybar/items/brew.sh39
-rw-r--r--mac/.config/sketchybar/items/cpu.sh74
-rw-r--r--mac/.config/sketchybar/items/datetime.sh37
-rw-r--r--mac/.config/sketchybar/items/disk.sh18
-rw-r--r--mac/.config/sketchybar/items/dnd.sh9
-rw-r--r--mac/.config/sketchybar/items/front_app.sh13
-rw-r--r--mac/.config/sketchybar/items/github.sh37
-rw-r--r--mac/.config/sketchybar/items/kakaotalk.sh18
-rw-r--r--mac/.config/sketchybar/items/keyboard.sh14
-rw-r--r--mac/.config/sketchybar/items/mail.sh19
-rw-r--r--mac/.config/sketchybar/items/memory.sh20
-rw-r--r--mac/.config/sketchybar/items/messages.sh13
-rw-r--r--mac/.config/sketchybar/items/mic.sh5
-rw-r--r--mac/.config/sketchybar/items/music.sh36
-rw-r--r--mac/.config/sketchybar/items/network.sh40
-rw-r--r--mac/.config/sketchybar/items/package_monitor.sh19
-rw-r--r--mac/.config/sketchybar/items/separator_right.sh13
-rw-r--r--mac/.config/sketchybar/items/spaces.sh56
-rw-r--r--mac/.config/sketchybar/items/spotify.sh201
-rw-r--r--mac/.config/sketchybar/items/svim.sh14
-rw-r--r--mac/.config/sketchybar/items/system.sh53
-rw-r--r--mac/.config/sketchybar/items/thunderbird.sh20
-rw-r--r--mac/.config/sketchybar/items/toggle_stats.sh11
-rw-r--r--mac/.config/sketchybar/items/volume.sh35
-rw-r--r--mac/.config/sketchybar/items/weather.sh23
-rw-r--r--mac/.config/sketchybar/items/wifi.sh39
-rw-r--r--mac/.config/sketchybar/items/yabai.sh21
-rw-r--r--mac/.config/sketchybar/plugins/battery.sh85
-rw-r--r--mac/.config/sketchybar/plugins/brew.sh104
-rw-r--r--mac/.config/sketchybar/plugins/disk.sh29
-rw-r--r--mac/.config/sketchybar/plugins/dnd.sh33
-rw-r--r--mac/.config/sketchybar/plugins/dndindicator.sh24
-rw-r--r--mac/.config/sketchybar/plugins/front_app.sh8
-rw-r--r--mac/.config/sketchybar/plugins/github.sh108
-rw-r--r--mac/.config/sketchybar/plugins/icon_map.sh486
-rw-r--r--mac/.config/sketchybar/plugins/kakaotalk.sh9
-rw-r--r--mac/.config/sketchybar/plugins/keyboard.sh13
-rw-r--r--mac/.config/sketchybar/plugins/mail.sh9
-rw-r--r--mac/.config/sketchybar/plugins/memory.sh49
-rw-r--r--mac/.config/sketchybar/plugins/messages.sh7
-rw-r--r--mac/.config/sketchybar/plugins/mic.sh9
-rw-r--r--mac/.config/sketchybar/plugins/mic_click.sh11
-rw-r--r--mac/.config/sketchybar/plugins/music.sh92
-rw-r--r--mac/.config/sketchybar/plugins/music/Cover-Default.pngbin0 -> 79352 bytes
-rw-r--r--mac/.config/sketchybar/plugins/music/Get-Artwork.applescript65
-rw-r--r--mac/.config/sketchybar/plugins/network.sh25
-rw-r--r--mac/.config/sketchybar/plugins/nextevent.applescript19
-rw-r--r--mac/.config/sketchybar/plugins/nextevent.sh55
-rw-r--r--mac/.config/sketchybar/plugins/space.py199
-rw-r--r--mac/.config/sketchybar/plugins/space.sh62
-rw-r--r--mac/.config/sketchybar/plugins/space_windows.sh20
-rw-r--r--mac/.config/sketchybar/plugins/spotify.sh147
-rw-r--r--mac/.config/sketchybar/plugins/svim.sh30
-rw-r--r--mac/.config/sketchybar/plugins/thunderbird.sh9
-rw-r--r--mac/.config/sketchybar/plugins/toggle_stats.sh63
-rw-r--r--mac/.config/sketchybar/plugins/volume.sh43
-rw-r--r--mac/.config/sketchybar/plugins/volume_click.sh57
-rw-r--r--mac/.config/sketchybar/plugins/weather.sh191
-rw-r--r--mac/.config/sketchybar/plugins/wifi.sh104
-rw-r--r--mac/.config/sketchybar/plugins/yabai.sh141
-rw-r--r--mac/.config/sketchybar/plugins/zen.sh43
-rw-r--r--mac/.config/sketchybar/sketchybarrc56
-rw-r--r--mac/.config/skhd/skhdrc215
-rw-r--r--mac/.config/vscode/argv.json20
-rw-r--r--mac/.config/yabai/yabairc102
-rwxr-xr-xmac/.local/bin/menu15
694 files changed, 105084 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d8a4dcd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# arch
+
+TheSiahxyz's dotfiles for Arch/Artix, Debian, and Mac.
diff --git a/ar/.config/LazyVim/.gitignore b/ar/.config/LazyVim/.gitignore
new file mode 100644
index 0000000..cc5457a
--- /dev/null
+++ b/ar/.config/LazyVim/.gitignore
@@ -0,0 +1,8 @@
+tt.*
+.tests
+doc/tags
+debug
+.repro
+foo.*
+*.log
+data
diff --git a/ar/.config/LazyVim/.neoconf.json b/ar/.config/LazyVim/.neoconf.json
new file mode 100644
index 0000000..7c48087
--- /dev/null
+++ b/ar/.config/LazyVim/.neoconf.json
@@ -0,0 +1,15 @@
+{
+ "neodev": {
+ "library": {
+ "enabled": true,
+ "plugins": true
+ }
+ },
+ "neoconf": {
+ "plugins": {
+ "lua_ls": {
+ "enabled": true
+ }
+ }
+ }
+}
diff --git a/ar/.config/LazyVim/LICENSE b/ar/.config/LazyVim/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/ar/.config/LazyVim/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/ar/.config/LazyVim/README.md b/ar/.config/LazyVim/README.md
new file mode 100644
index 0000000..185280b
--- /dev/null
+++ b/ar/.config/LazyVim/README.md
@@ -0,0 +1,4 @@
+# 💤 LazyVim
+
+A starter template for [LazyVim](https://github.com/LazyVim/LazyVim).
+Refer to the [documentation](https://lazyvim.github.io/installation) to get started.
diff --git a/ar/.config/LazyVim/db_ui/connections.json b/ar/.config/LazyVim/db_ui/connections.json
new file mode 100644
index 0000000..e80cf62
--- /dev/null
+++ b/ar/.config/LazyVim/db_ui/connections.json
@@ -0,0 +1 @@
+[{"url": "mysql://si@localhost:3306/si", "name": "si"}, {"url": "mysql://root@localhost/test", "name": "test"}]
diff --git a/ar/.config/LazyVim/init.lua b/ar/.config/LazyVim/init.lua
new file mode 100644
index 0000000..bf21bdc
--- /dev/null
+++ b/ar/.config/LazyVim/init.lua
@@ -0,0 +1,3 @@
+-- bootstrap lazy.nvim, LazyVim and your plugins
+-- In init.lua
+require("config.lazy")
diff --git a/ar/.config/LazyVim/lua/config/autocmds.lua b/ar/.config/LazyVim/lua/config/autocmds.lua
new file mode 100644
index 0000000..6550daf
--- /dev/null
+++ b/ar/.config/LazyVim/lua/config/autocmds.lua
@@ -0,0 +1,157 @@
+--------------------------------------------------------------
+-- ######################################################## --
+-- ################## Custom #################### --
+-- ######################################################## --
+--------------------------------------------------------------
+
+-- Save file as sudo on files that require root permission
+vim.api.nvim_create_user_command("SudoWrite", function()
+ vim.cmd("write !sudo tee % >/dev/null")
+ vim.cmd("edit!")
+end, {})
+
+-- Enable Goyo by default for mutt writing
+local goyo_group = vim.api.nvim_create_augroup("GoyoForMutt", { clear = true })
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = "/tmp/neomutt*",
+ group = goyo_group,
+ callback = function()
+ vim.g.goyo_width = 80
+ vim.cmd("Goyo")
+ vim.cmd("set bg=light")
+ vim.cmd("set linebreak")
+ vim.cmd("set wrap")
+ vim.cmd("set textwidth=0")
+ vim.cmd("set wrapmargin=0")
+ vim.cmd("colorscheme seoul256")
+ vim.api.nvim_buf_set_keymap(
+ 0,
+ "n",
+ "<leader>gx",
+ ":Goyo|x!<CR>",
+ { noremap = true, silent = true, desc = "Goyo Quit" }
+ )
+ vim.api.nvim_buf_set_keymap(
+ 0,
+ "n",
+ "<leader>gq",
+ ":Goyo|q!<CR>",
+ { noremap = true, silent = true, desc = "Goyo Abort" }
+ )
+ end,
+})
+
+-- Vimwiki
+-- Ensure files are read with the desired filetype
+vim.g.vimwiki_ext2syntax = {
+ [".Rmd"] = "markdown",
+ [".rmd"] = "markdown",
+ [".md"] = "markdown",
+ [".markdown"] = "markdown",
+ [".mdown"] = "markdown",
+}
+-- Set up Vimwiki list
+vim.g.vimwiki_list = { {
+ path = vim.fn.expand("~/.local/share/vimwiki"),
+ syntax = "markdown",
+ ext = ".md",
+} }
+-- Markdown for specific files and directories
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "/tmp/calcurse*", "~/.calcurse/notes/*" },
+ command = "set filetype=markdown",
+})
+
+-- Groff for specific file extensions
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "*.ms", "*.me", "*.mom", "*.man" },
+ command = "set filetype=groff",
+})
+
+-- TeX for .tex files
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "*.tex" },
+ command = "set filetype=tex",
+})
+
+-- When shortcut files are updated, renew bash and lf configs with new material:
+local config_group = vim.api.nvim_create_augroup("ConfigUpdate", { clear = true })
+vim.api.nvim_create_autocmd("BufWritePost", {
+ pattern = { "bm-files", "bm-dirs" },
+ group = config_group,
+ callback = function()
+ -- Execute the 'shortcuts' shell command
+ vim.fn.system("shortcuts")
+
+ -- Check if the 'shortcuts' command was successful
+ if vim.v.shell_error == 0 then
+ -- Display a message in Neovim
+ vim.api.nvim_echo({ { "shortcuts updated", "None" } }, true, {})
+ else
+ -- Optional: Display an error message if the 'shortcuts' command fails
+ vim.api.nvim_echo({ { "failed to update shortcuts", "ErrorMsg" } }, true, {})
+ end
+ end,
+})
+
+-- Run xrdb whenever Xdefaults or Xresources are updated.
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "Xresources", "Xdefaults", "xresources", "xdefaults" },
+ group = config_group,
+ callback = function()
+ vim.bo.filetype = "xdefaults"
+ end,
+})
+vim.api.nvim_create_autocmd("BufWritePost", {
+ pattern = { "Xresources", "Xdefaults", "xresources", "xdefaults" },
+ group = config_group,
+ callback = function()
+ vim.cmd("!xrdb %")
+ end,
+})
+
+-- Recompile dwmblocks on config edit.
+local home = os.getenv("HOME") -- Gets the home directory
+local dwmblocks_path = home .. "/.local/src/suckless/dwmblocks/config.h"
+vim.api.nvim_create_autocmd("BufWritePost", {
+ pattern = dwmblocks_path,
+ group = vim.api.nvim_create_augroup("DwmblocksConfigGroup", { clear = true }),
+ callback = function()
+ vim.cmd(
+ "!cd "
+ .. home
+ .. "/.local/src/suckless/dwmblocks/ && sudo make install && { killall -q dwmblocks; setsid -f dwmblocks; }"
+ )
+ end,
+})
+
+-- Autocommand group for DWM
+vim.api.nvim_create_augroup("DwmConfigGroup", { clear = true })
+vim.api.nvim_create_autocmd("BufWritePost", {
+ pattern = home .. "/.local/src/suckless/dwm/config.h",
+ group = "DwmConfigGroup",
+ callback = function()
+ vim.cmd("!extractkeys")
+ end,
+})
+
+-- Autocommand group for ST
+vim.api.nvim_create_augroup("StConfigGroup", { clear = true })
+vim.api.nvim_create_autocmd("BufWritePost", {
+ pattern = home .. "/.local/src/suckless/st/config.h",
+ group = "StConfigGroup",
+ callback = function()
+ vim.cmd("!extractkeys")
+ end,
+})
+
+vim.api.nvim_create_autocmd({ "BufRead", "BufEnter" }, {
+ pattern = { "*.c", "*.cpp", "*.h", "*.hpp" },
+ callback = function()
+ local suckless_path = vim.fn.expand("~/.local/src"):gsub("/+$", "")
+ local file_path = vim.fn.expand("%:p"):gsub("/+$", "")
+ if file_path == suckless_path or file_path:find("^" .. suckless_path .. "/") then
+ vim.b.autoformat = false
+ end
+ end,
+})
diff --git a/ar/.config/LazyVim/lua/config/keymaps.lua b/ar/.config/LazyVim/lua/config/keymaps.lua
new file mode 100644
index 0000000..e985c9d
--- /dev/null
+++ b/ar/.config/LazyVim/lua/config/keymaps.lua
@@ -0,0 +1,275 @@
+local function word_definition(input)
+ if input == "" then
+ input = vim.fn.getreg(vim.fn.visualmode(), 1, 1)
+ end
+
+ local escaped_input = vim.fn.shellescape(input)
+ local output = vim.fn.system("dict " .. escaped_input)
+
+ vim.api.nvim_out_write(output)
+end
+
+-- source shortcuts from bm-files and bm-folders
+local shortcuts_file = vim.fn.expand("~/.config/nvim/shortcuts.lua")
+local file = io.open(shortcuts_file, "r")
+if file then
+ file:close() -- It's important to close the file after opening it.
+ vim.cmd("silent! source " .. shortcuts_file)
+end
+
+-- word definition
+vim.api.nvim_set_keymap(
+ "n",
+ "<leader>fd",
+ ":lua word_definition(vim.fn.expand('<cword>'))<CR>",
+ { noremap = true, silent = true, desc = "Word Definition" }
+)
+vim.api.nvim_set_keymap(
+ "x",
+ "<leader>fd",
+ ":lua word_definition(vim.fn.getreg('v', 1, 1))<CR>",
+ { noremap = true, silent = true, desc = "Word Definition in Visual Mode" }
+)
+
+--------------------------------------
+-- Disable
+vim.keymap.del("n", "<C-F>")
+vim.keymap.del("n", "<leader><tab>]")
+vim.keymap.del("n", "<leader><tab>d")
+vim.keymap.del("n", "<leader><tab>[")
+vim.keymap.del("n", "<leader>wd")
+vim.keymap.del("n", "<leader>ww")
+
+-- vim.keymap.set( "n", "J", "<Nop>", { noremap = true, silent = true })
+vim.keymap.set("n", "Q", "<nop>")
+
+-- ESC
+vim.keymap.set("i", "jk", "<ESC>", { desc = "<ESC>" })
+
+-- :
+vim.keymap.set("n", "<C-c>", ":", { desc = "Enter Command Mode", nowait = true })
+
+-- Check Health
+vim.keymap.set("n", "<leader>ch", ":checkhealth<cr>", { desc = "Check Health" })
+
+-- Copy & Cut & Paste
+vim.keymap.set("n", "x", '"_x')
+-- vim.keymap.set("n", "d", '"_d')
+-- vim.keymap.set("n", "D", '"_D')
+-- vim.keymap.set("v", "d", '"_d')
+-- vim.keymap.set("n", "<leader>d", '""d')
+-- vim.keymap.set("n", "<leader>D", '""D')
+-- vim.keymap.set("v", "<leader>d", '""d')
+vim.keymap.set("n", "<leader>a", "ggVG", { desc = "Select Whole File" })
+vim.keymap.set("n", "<leader>cpa", "<cmd>%y+<cr>", { desc = "Copy Whole File" })
+vim.keymap.set("x", "p", 'p:let @+=@0<CR>:let @"=@0<CR>', { silent = true })
+
+-- Lines
+vim.keymap.set("i", "<C-cr>", "<Esc>$o", { nowait = true, silent = true })
+vim.keymap.set("i", "<C-f>", "<ESC>o", { noremap = true, nowait = true, silent = true, desc = "Next Line" })
+vim.keymap.set("i", "<C-b>", "<ESC>O", { noremap = true, nowait = true, silent = true, desc = "Previous Line" })
+-- vim.keymap.set(
+-- "v",
+-- "<C-j>",
+-- ":m '>+1<CR>gv=gv",
+-- { noremap = true, nowait = true, silent = true, desc = "Move A Line To Bottom" }
+-- )
+-- vim.keymap.set(
+-- "v",
+-- "<C-k>",
+-- ":m '<-2<CR>gv=gv",
+-- { noremap = true, nowait = true, silent = true, desc = "Move A Line To Up" }
+-- )
+
+-- Navigation
+vim.keymap.set("i", "<C-i>", "<ESC>I", { desc = "Beginning of Line" })
+vim.keymap.set("i", "<C-a>", "<End>", { desc = "End of Line" })
+vim.keymap.set("i", "<C-h>", "<Left>", { desc = "Move Left" })
+vim.keymap.set("i", "<C-l>", "<Right>", { desc = "Move Right" })
+vim.keymap.set("i", "<C-j>", "<Down>", { desc = "Move Down" })
+vim.keymap.set("i", "<C-k>", "<Up>", { desc = "Move Up" })
+
+-- better up/down
+vim.keymap.set({ "n", "x" }, "j", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true, desc = "Better Down" })
+vim.keymap.set(
+ { "n", "x" },
+ "<Down>",
+ "v:count == 0 ? 'gj' : 'j'",
+ { expr = true, silent = true, desc = "Better Down" }
+)
+vim.keymap.set({ "n", "x" }, "k", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true, desc = "Better Up" })
+vim.keymap.set({ "n", "x" }, "<Up>", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true, desc = "Better Up" })
+
+-- -- Move to window using the <ctrl> hjkl keys
+-- vim.keymap.set("n", "<C-h>", "<C-w>h", { desc = "Go to left window", remap = true })
+-- vim.keymap.set("n", "<C-j>", "<C-w>j", { desc = "Go to lower window", remap = true })
+-- vim.keymap.set("n", "<C-k>", "<C-w>k", { desc = "Go to upper window", remap = true })
+-- vim.keymap.set("n", "<C-l>", "<C-w>l", { desc = "Go to right window", remap = true })
+--
+-- -- Resize window using <ctrl> arrow keys
+-- vim.keymap.set("n", "<C-Up>", "<cmd>resize +2<cr>", { desc = "Increase window height" })
+-- vim.keymap.set("n", "<C-Down>", "<cmd>resize -2<cr>", { desc = "Decrease window height" })
+-- vim.keymap.set("n", "<C-Left>", "<cmd>vertical resize -2<cr>", { desc = "Decrease window width" })
+-- vim.keymap.set("n", "<C-Right>", "<cmd>vertical resize +2<cr>", { desc = "Increase window width" })
+--
+-- -- Move Lines
+-- vim.keymap.set("n", "<A-J>", "<cmd>m .+1<cr>==", { desc = "Move down" })
+-- vim.keymap.set("n", "<A-K>", "<cmd>m .-2<cr>==", { desc = "Move up" })
+-- vim.keymap.set("i", "<A-J>", "<esc><cmd>m .+1<cr>==gi", { desc = "Move down" })
+-- vim.keymap.set("i", "<A-K>", "<esc><cmd>m .-2<cr>==gi", { desc = "Move up" })
+-- vim.keymap.set("v", "<A-J>", ":m '>+1<cr>gv=gv", { desc = "Move down" })
+-- vim.keymap.set("v", "<A-K>", ":m '<-2<cr>gv=gv", { desc = "Move up" })
+-- vim.keymap.set("n", "<C-A-n>", "<cmd>m .+1<cr>==", { desc = "Move down" })
+-- vim.keymap.set("n", "<C-A-p>", "<cmd>m .-2<cr>==", { desc = "Move up" })
+-- vim.keymap.set("i", "<C-A-n>", "<esc><cmd>m .+1<cr>==gi", { desc = "Move down" })
+-- vim.keymap.set("i", "<C-A-p>", "<esc><cmd>m .-2<cr>==gi", { desc = "Move up" })
+-- vim.keymap.set("v", "<C-A-n>", ":m '>+1<cr>gv=gv", { desc = "Move down" })
+-- vim.keymap.set("v", "<C-A-p>", ":m '<-2<cr>gv=gv", { desc = "Move up" })
+vim.keymap.set("n", "<A-,>", "<cmd>m .+1<cr>==", { desc = "Move down" })
+vim.keymap.set("n", "<A-.>", "<cmd>m .-2<cr>==", { desc = "Move up" })
+vim.keymap.set("i", "<A-,>", "<esc><cmd>m .+1<cr>==gi", { desc = "Move down" })
+vim.keymap.set("i", "<A-.>", "<esc><cmd>m .-2<cr>==gi", { desc = "Move up" })
+vim.keymap.set("v", "<A-,>", ":m '>+1<cr>gv=gv", { desc = "Move down" })
+vim.keymap.set("v", "<A-.>", ":m '<-2<cr>gv=gv", { desc = "Move up" })
+vim.keymap.set("n", "<C-b>", "<C-b>zz")
+vim.keymap.set("n", "<C-f>", "<C-f>zz")
+--
+-- -- buffers
+-- vim.keymap.set("n", "<S-h>", "<cmd>bprevious<cr>", { desc = "Prev buffer" })
+-- vim.keymap.set("n", "<S-l>", "<cmd>bnext<cr>", { desc = "Next buffer" })
+-- vim.keymap.set("n", "[b", "<cmd>bprevious<cr>", { desc = "Prev buffer" })
+-- vim.keymap.set("n", "]b", "<cmd>bnext<cr>", { desc = "Next buffer" })
+-- vim.keymap.set("n", "<leader>bb", "<cmd>e #<cr>", { desc = "Switch to Other Buffer" })
+-- vim.keymap.set("n", "<leader>`", "<cmd>e #<cr>", { desc = "Switch to Other Buffer" })
+--
+-- -- Clear search with <esc>
+-- vim.keymap.set({ "i", "n" }, "<esc>", "<cmd>noh<cr><esc>", { desc = "Escape and clear hlsearch" })
+--
+-- -- Clear search, diff update and redraw
+-- -- taken from runtime/lua/_editor.lua
+-- vim.keymap.set(
+-- "n",
+-- "<leader>ur",
+-- "<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>",
+-- { desc = "Redraw / clear hlsearch / diff update" }
+-- )
+--
+-- -- https://github.com/mhinz/vim-galore#saner-behavior-of-n-and-n
+-- vim.keymap.set("n", "n", "'Nn'[v:searchforward].'zv'", { expr = true, desc = "Next search result" })
+-- vim.keymap.set("x", "n", "'Nn'[v:searchforward]", { expr = true, desc = "Next search result" })
+-- vim.keymap.set("o", "n", "'Nn'[v:searchforward]", { expr = true, desc = "Next search result" })
+-- vim.keymap.set("n", "N", "'nN'[v:searchforward].'zv'", { expr = true, desc = "Prev search result" })
+-- vim.keymap.set("x", "N", "'nN'[v:searchforward]", { expr = true, desc = "Prev search result" })
+-- vim.keymap.set("o", "N", "'nN'[v:searchforward]", { expr = true, desc = "Prev search result" })
+--
+-- -- Add undo break-points
+-- vim.keymap.set("i", ",", ",<c-g>u")
+-- vim.keymap.set("i", ".", ".<c-g>u")
+-- vim.keymap.set("i", ";", ";<c-g>u")
+--
+-- -- save file
+-- vim.keymap.set({ "i", "x", "n", "s" }, "<C-s>", "<cmd>w<cr><esc>", { desc = "Save file" })
+vim.keymap.set({ "n" }, "<leader>W", "<cmd>SudoWrite<cr><esc>", { desc = "Save readonly file" })
+--
+-- --keywordprg
+-- vim.keymap.set("n", "<leader>K", "<cmd>norm! K<cr>", { desc = "Keywordprg" })
+--
+-- -- better indenting
+-- vim.keymap.set("v", "<", "<gv")
+-- vim.keymap.set("v", ">", ">gv")
+--
+-- -- lazy
+-- vim.keymap.set("n", "<leader>l", "<cmd>Lazy<cr>", { desc = "Lazy" })
+--
+-- -- new file
+-- vim.keymap.set("n", "<leader>fn", "<cmd>enew<cr>", { desc = "New File" })
+--
+-- vim.keymap.set("n", "<leader>xl", "<cmd>lopen<cr>", { desc = "Location List" })
+-- vim.keymap.set("n", "<leader>xq", "<cmd>copen<cr>", { desc = "Quickfix List" })
+--
+-- vim.keymap.set("n", "[q", vim.cmd.cprev, { desc = "Previous quickfix" })
+-- vim.keymap.set("n", "]q", vim.cmd.cnext, { desc = "Next quickfix" })
+--
+-- -- formatting
+-- vim.keymap.set({ "n", "v" }, "<leader>cf", function()
+-- Util.format({ force = true })
+-- end, { desc = "Format" })
+--
+-- -- diagnostic
+-- local diagnostic_goto = function(next, severity)
+-- local go = next and vim.diagnostic.goto_next or vim.diagnostic.goto_prev
+-- severity = severity and vim.diagnostic.severity[severity] or nil
+-- return function()
+-- go({ severity = severity })
+-- end
+-- end
+-- vim.keymap.set("n", "<leader>cd", vim.diagnostic.open_float, { desc = "Line Diagnostics" })
+-- vim.keymap.set("n", "]d", diagnostic_goto(true), { desc = "Next Diagnostic" })
+-- vim.keymap.set("n", "[d", diagnostic_goto(false), { desc = "Prev Diagnostic" })
+-- vim.keymap.set("n", "]e", diagnostic_goto(true, "ERROR"), { desc = "Next Error" })
+-- vim.keymap.set("n", "[e", diagnostic_goto(false, "ERROR"), { desc = "Prev Error" })
+-- vim.keymap.set("n", "]w", diagnostic_goto(true, "WARN"), { desc = "Next Warning" })
+-- vim.keymap.set("n", "[w", diagnostic_goto(false, "WARN"), { desc = "Prev Warning" })
+--
+-- -- stylua: ignore start
+--
+-- -- toggle options
+-- vim.keymap.set("n", "<leader>uf", function() Util.format.toggle() end, { desc = "Toggle auto format (global)" })
+-- vim.keymap.set("n", "<leader>uF", function() Util.format.toggle(true) end, { desc = "Toggle auto format (buffer)" })
+-- vim.keymap.set("n", "<leader>us", function() Util.toggle("spell") end, { desc = "Toggle Spelling" })
+-- vim.keymap.set("n", "<leader>uw", function() Util.toggle("wrap") end, { desc = "Toggle Word Wrap" })
+-- vim.keymap.set("n", "<leader>uL", function() Util.toggle("relativenumber") end, { desc = "Toggle Relative Line Numbers" })
+-- vim.keymap.set("n", "<leader>ul", function() Util.toggle.number() end, { desc = "Toggle Line Numbers" })
+-- vim.keymap.set("n", "<leader>ud", function() Util.toggle.diagnostics() end, { desc = "Toggle Diagnostics" })
+-- local conceallevel = vim.o.conceallevel > 0 and vim.o.conceallevel or 3
+-- vim.keymap.set("n", "<leader>uc", function() Util.toggle("conceallevel", false, {0, conceallevel}) end, { desc = "Toggle Conceal" })
+-- if vim.lsp.buf.inlay_hint or vim.lsp.inlay_hint then
+-- map( "n", "<leader>uh", function() Util.toggle.inlay_hints() end, { desc = "Toggle Inlay Hints" })
+-- end
+-- vim.keymap.set("n", "<leader>uT", function() if vim.b.ts_highlight then vim.treesitter.stop() else vim.treesitter.start() end end, { desc = "Toggle Treesitter Highlight" })
+--
+-- -- lazygit
+-- vim.keymap.set("n", "<leader>gg", function() Util.terminal({ "lazygit" }, { cwd = Util.root(), esc_esc = false, ctrl_hjkl = false }) end, { desc = "Lazygit (root dir)" })
+-- vim.keymap.set("n", "<leader>gG", function() Util.terminal({ "lazygit" }, {esc_esc = false, ctrl_hjkl = false}) end, { desc = "Lazygit (cwd)" })
+--
+-- -- quit
+-- vim.keymap.set("n", "<leader>qq", "<cmd>qa<cr>", { desc = "Quit all" })
+--
+-- -- highlights under cursor
+-- vim.keymap.set("n", "<leader>ui", vim.show_pos, { desc = "Inspect Pos" })
+--
+-- -- LazyVim Changelog
+-- vim.keymap.set("n", "<leader>L", function() Util.news.changelog() end, { desc = "LazyVim Changelog" })
+--
+-- -- floating terminal
+-- local lazyterm = function() Util.terminal(nil, { cwd = Util.root() }) end
+-- vim.keymap.set("n", "<leader>ft", lazyterm, { desc = "Terminal (root dir)" })
+-- vim.keymap.set("n", "<leader>fT", function() Util.terminal() end, { desc = "Terminal (cwd)" })
+-- vim.keymap.set("n", "<c-/>", lazyterm, { desc = "Terminal (root dir)" })
+-- vim.keymap.set("n", "<c-_>", lazyterm, { desc = "which_key_ignore" })
+--
+-- -- Terminal Mappings
+-- vim.keymap.set("t", "<esc><esc>", "<c-\\><c-n>", { desc = "Enter Normal Mode" })
+-- vim.keymap.set("t", "<C-h>", "<cmd>wincmd h<cr>", { desc = "Go to left window" })
+-- vim.keymap.set("t", "<C-j>", "<cmd>wincmd j<cr>", { desc = "Go to lower window" })
+-- vim.keymap.set("t", "<C-k>", "<cmd>wincmd k<cr>", { desc = "Go to upper window" })
+-- vim.keymap.set("t", "<C-l>", "<cmd>wincmd l<cr>", { desc = "Go to right window" })
+-- vim.keymap.set("t", "<C-/>", "<cmd>close<cr>", { desc = "Hide Terminal" })
+-- vim.keymap.set("t", "<c-_>", "<cmd>close<cr>", { desc = "which_key_ignore" })
+--
+-- -- windows
+vim.keymap.set("n", "<leader>w=", "<C-W>=", { desc = "Equal window", remap = true })
+vim.keymap.set("n", "<leader>wo", "<C-W>p", { desc = "Other window", remap = true })
+vim.keymap.set("n", "<leader>wx", "<C-W>c", { desc = "Close window", remap = true })
+-- vim.keymap.set("n", "<leader>w-", "<C-W>s", { desc = "Split window below", remap = true })
+-- vim.keymap.set("n", "<leader>w|", "<C-W>v", { desc = "Split window right", remap = true })
+-- vim.keymap.set("n", "<leader>-", "<C-W>s", { desc = "Split window below", remap = true })
+-- vim.keymap.set("n", "<leader>|", "<C-W>v", { desc = "Split window right", remap = true })
+--
+-- -- tabs
+-- vim.keymap.set("n", "<leader><tab>l", "<cmd>tablast<cr>", { desc = "Last Tab" })
+-- vim.keymap.set("n", "<leader><tab>f", "<cmd>tabfirst<cr>", { desc = "First Tab" })
+-- vim.keymap.set("n", "<leader><tab><tab>", "<cmd>tabnew<cr>", { desc = "New Tab" })
+vim.keymap.set("n", "<leader><tab>n", "<cmd>tabnext<cr>", { desc = "Next Tab" })
+vim.keymap.set("n", "<leader><tab>x", "<cmd>tabclose<cr>", { desc = "Close Tab" })
+vim.keymap.set("n", "<leader><tab>p", "<cmd>tabprevious<cr>", { desc = "Previous Tab" })
diff --git a/ar/.config/LazyVim/lua/config/lazy.lua b/ar/.config/LazyVim/lua/config/lazy.lua
new file mode 100644
index 0000000..b52de86
--- /dev/null
+++ b/ar/.config/LazyVim/lua/config/lazy.lua
@@ -0,0 +1,105 @@
+local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
+if not vim.loop.fs_stat(lazypath) then
+ -- bootstrap lazy.nvim
+ -- stylua: ignore
+ vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
+end
+vim.opt.rtp:prepend(vim.env.LAZY or lazypath)
+
+require("lazy").setup({
+ spec = {
+ -- add LazyVim and import its plugins
+ { "LazyVim/LazyVim", import = "lazyvim.plugins" },
+ -- import any extras modules here
+ -- coding
+ -- { import = "lazyvim.plugins.extras.coding.codeium" },
+ -- { import = "lazyvim.plugins.extras.coding.copilot" },
+ -- { import = "lazyvim.plugins.extras.coding.native_snippets" },
+ -- { import = "lazyvim.plugins.extras.coding.tabnine" },
+ -- { import = "lazyvim.plugins.extras.coding.yanky" },
+ -- dap
+ { import = "lazyvim.plugins.extras.dap.core" },
+ { import = "lazyvim.plugins.extras.dap.nlua" },
+ -- editor
+ -- { import = "lazyvim.plugins.extras.editor.aerial" },
+ { import = "lazyvim.plugins.extras.editor.harpoon2" },
+ { import = "lazyvim.plugins.extras.editor.leap" },
+ { import = "lazyvim.plugins.extras.editor.mini-files" },
+ -- { import = "lazyvim.plugins.extras.editor.navic" },
+ -- { import = "lazyvim.plugins.extras.editor.outline" },
+ -- formatting
+ { import = "lazyvim.plugins.extras.formatting.black" },
+ { import = "lazyvim.plugins.extras.formatting.prettier" },
+ -- lang
+ { import = "lazyvim.plugins.extras.lang.ansible" },
+ { import = "lazyvim.plugins.extras.lang.clangd" },
+ -- { import = "lazyvim.plugins.extras.lang.cmake" },
+ { import = "lazyvim.plugins.extras.lang.docker" },
+ -- { import = "lazyvim.plugins.extras.lang.elixir" },
+ -- { import = "lazyvim.plugins.extras.lang.go" },
+ -- { import = "lazyvim.plugins.extras.lang.haskell" },
+ -- { import = "lazyvim.plugins.extras.lang.helm" },
+ { import = "lazyvim.plugins.extras.lang.java" },
+ { import = "lazyvim.plugins.extras.lang.json" },
+ { import = "lazyvim.plugins.extras.lang.markdown" },
+ -- { import = "lazyvim.plugins.extras.lang.omnisharp" },
+ -- { import = "lazyvim.plugins.extras.lang.python-semshi" },
+ { import = "lazyvim.plugins.extras.lang.python" },
+ -- { import = "lazyvim.plugins.extras.lang.ruby" },
+ -- { import = "lazyvim.plugins.extras.lang.rust" },
+ -- { import = "lazyvim.plugins.extras.lang.scala" },
+ -- { import = "lazyvim.plugins.extras.lang.tailwind" },
+ -- { import = "lazyvim.plugins.extras.lang.terraform" },
+ -- { import = "lazyvim.plugins.extras.lang.tex" },
+ -- { import = "lazyvim.plugins.extras.lang.typescript" },
+ { import = "lazyvim.plugins.extras.lang.yaml" },
+ -- linting
+ { import = "lazyvim.plugins.extras.linting.eslint" },
+ -- lsp
+ { import = "lazyvim.plugins.extras.lsp.none-ls" },
+ -- test
+ { import = "lazyvim.plugins.extras.test.core" },
+ -- ui
+ -- { import = "lazyvim.plugins.extras.ui.alpha" },
+ { import = "lazyvim.plugins.extras.ui.edgy" },
+ -- { import = "lazyvim.plugins.extras.ui.mini-animate" },
+ -- { import = "lazyvim.plugins.extras.ui.mini-starter" },
+ -- util
+ -- { import = "lazyvim.plugins.extras.util.dot" },
+ -- { import = "lazyvim.plugins.extras.util.gitui" },
+ { import = "lazyvim.plugins.extras.util.mini-hipatterns" },
+ { import = "lazyvim.plugins.extras.util.project" },
+ -- extras
+ { import = "lazyvim.plugins.extras.lazyrc" },
+ { import = "lazyvim.plugins.extras.vscode" },
+
+ -- import/override with your plugins
+ { import = "plugins" },
+ },
+ defaults = {
+ -- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
+ -- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
+ lazy = false,
+ -- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
+ -- have outdated releases, which may break your Neovim install.
+ version = false, -- always use the latest git commit
+ -- version = "*", -- try installing the latest stable version for plugins that support semver
+ },
+ install = { colorscheme = { "tokyonight", "habamax" } },
+ checker = { enabled = true }, -- automatically check for plugin updates
+ performance = {
+ rtp = {
+ -- disable some rtp plugins
+ disabled_plugins = {
+ "gzip",
+ -- "matchit",
+ -- "matchparen",
+ -- "netrwPlugin",
+ "tarPlugin",
+ "tohtml",
+ "tutor",
+ "zipPlugin",
+ },
+ },
+ },
+})
diff --git a/ar/.config/LazyVim/lua/config/options.lua b/ar/.config/LazyVim/lua/config/options.lua
new file mode 100644
index 0000000..1172763
--- /dev/null
+++ b/ar/.config/LazyVim/lua/config/options.lua
@@ -0,0 +1,105 @@
+-- Options are automatically loaded before lazy.nvim startup
+-- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua
+-- Add any additional options here
+
+-- vim.g.mapleader = " "
+vim.g.maplocalleader = "\\"
+-- vim.g.autoformat = true
+-- vim.g.root_spec = { "lsp", { ".git", "lua" }, "cwd" }
+-- vim.g.netrw_banner = 0
+-- vim.g.netrw_mouse = 2
+
+-- vim.opt.autowrite = true -- Enable auto write
+vim.opt.backup = false -- creates a backup file
+-- vim.opt.clipboard = "" -- "unnamedplus" -- Sync with system clipboard
+vim.opt.cmdheight = 1
+vim.opt.colorcolumn = "85"
+-- vim.opt.completeopt = "menu,menuone,noselect"
+vim.opt.conceallevel = 1 -- default = 3, Hide * markup for bold and italic
+-- vim.opt.confirm = true -- Confirm to save changes before exiting modified buffer
+-- vim.opt.cursorline = true -- Enable highlighting of the current line
+-- vim.opt.expandtab = true -- Use spaces instead of tabs
+-- vim.opt.fileencoding = "utf-8"
+vim.opt.hlsearch = true
+-- vim.opt.formatoptions = "jcroqlnt" -- tcqj
+-- vim.opt.grepformat = "%f:%l:%c:%m"
+-- vim.opt.grepprg = "rg --vimgrep"
+-- vim.opt.guifont = "monospace:h17"
+-- vim.opt.ignorecase = true -- Ignore case
+-- vim.opt.inccommand = "nosplit" -- preview incremental substitute
+-- vim.opt.laststatus = 3 -- global statusline
+-- vim.opt.list = true -- Show some invisible characters (tabs...
+-- vim.opt.mouse = "a" -- Enable mouse mode
+-- vim.opt.number = true -- Print line number
+-- vim.opt.numberwidth = 4
+-- vim.opt.pumblend = 10 -- Popup blend
+-- vim.opt.pumheight = 10 -- Maximum number of entries in a popup
+-- vim.opt.relativenumber = true -- Relative line numbers
+vim.opt.ruler = false
+vim.opt.scrolloff = 8 -- Lines of context
+-- vim.opt.sessionoptions = { "buffers", "curdir", "tabpages", "winsize", "help", "globals", "skiprtp", "folds" }
+-- vim.opt.shiftround = true -- Round indent
+-- vim.opt.shiftwidth = 2 -- Size of an indent
+-- vim.opt.shortmess:append({ W = true, I = true, c = true, C = true })
+vim.opt.showcmd = false
+-- vim.opt.showmode = false -- Dont show mode since we have a statusline
+vim.opt.showtabline = 1
+-- vim.opt.sidescrolloff = 8 -- Columns of context
+-- vim.opt.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time
+-- vim.opt.smartcase = true -- Don't ignore case with capitals
+-- vim.opt.smartindent = true -- Insert indents automatically
+-- vim.opt.spelllang = { "en" }
+-- vim.opt.splitbelow = true -- Put new windows below current
+-- vim.opt.splitkeep = "screen"
+-- vim.opt.splitright = true -- Put new windows right of current
+vim.opt.swapfile = false
+vim.opt.tabstop = 4 -- Number of spaces tabs count for
+-- vim.opt.termguicolors = true -- True color support
+-- vim.opt.timeoutlen = 300
+vim.opt.title = false
+-- vim.opt.undofile = true
+-- vim.opt.undolevels = 10000
+-- vim.opt.updatetime = 200 -- Save swap file and trigger CursorHold
+-- vim.opt.virtualedit = "block" -- Allow cursor to move where there is no text in visual block mode
+-- vim.opt.wildmode = "longest:full,full" -- Command-line completion mode
+-- vim.opt.winminwidth = 5 -- Minimum window width
+-- vim.opt.wrap = false -- Disable line wrap
+vim.opt.writebackup = false
+
+vim.cmd("set whichwrap+=<,>,[,],h,l")
+vim.cmd([[set iskeyword+=-]])
+
+-- vim.opt.fillchars = {
+-- foldopen = "",
+-- foldclose = "",
+-- -- fold = "⸱",
+-- fold = " ",
+-- foldsep = " ",
+-- diff = "╱",
+-- eob = " ",
+-- }
+--
+-- if vim.fn.has("nvim-0.10") == 1 then
+-- vim.opt.smoothscroll = true
+-- end
+--
+-- -- Folding
+-- vim.opt.foldlevel = 99
+-- vim.opt.foldtext = "v:lua.require'lazyvim.util'.ui.foldtext()"
+--
+-- if vim.fn.has("nvim-0.9.0") == 1 then
+-- vim.opt.statuscolumn = [[%!v:lua.require'lazyvim.util'.ui.statuscolumn()]]
+-- end
+--
+-- -- HACK: causes freezes on <= 0.9, so only enable on >= 0.10 for now
+-- if vim.fn.has("nvim-0.10") == 1 then
+-- vim.opt.foldmethod = "expr"
+-- vim.opt.foldexpr = "v:lua.require'lazyvim.util'.ui.foldexpr()"
+-- else
+-- vim.opt.foldmethod = "indent"
+-- end
+--
+-- vim.o.formatexpr = "v:lua.require'lazyvim.util'.format.formatexpr()"
+--
+-- -- Fix markdown indentation settings
+-- vim.g.markdown_recommended_style = 0
diff --git a/ar/.config/LazyVim/lua/plugins/better-escape.lua b/ar/.config/LazyVim/lua/plugins/better-escape.lua
new file mode 100644
index 0000000..8980a09
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/better-escape.lua
@@ -0,0 +1,6 @@
+return {
+ "max397574/better-escape.nvim",
+ config = function()
+ require("better_escape").setup()
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/bqf.lua b/ar/.config/LazyVim/lua/plugins/bqf.lua
new file mode 100644
index 0000000..529c06d
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/bqf.lua
@@ -0,0 +1,9 @@
+return {
+ "kevinhwang91/nvim-bqf",
+ ft = "qf",
+ keys = {
+ -- { "zn", false },
+ -- { "zN", false },
+ -- { "zf", false },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/catppuccin.lua b/ar/.config/LazyVim/lua/plugins/catppuccin.lua
new file mode 100644
index 0000000..fa451bd
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/catppuccin.lua
@@ -0,0 +1,92 @@
+return {
+ "catppuccin/nvim",
+ lazy = true,
+ name = "catppuccin",
+ priority = 1000,
+ opts = {
+ integrations = {
+ aerial = true,
+ alpha = true,
+ cmp = true,
+ dashboard = true,
+ flash = true,
+ gitsigns = true,
+ headlines = true,
+ illuminate = true,
+ indent_blankline = { enabled = true },
+ leap = true,
+ lsp_trouble = true,
+ mason = true,
+ markdown = true,
+ mini = true,
+ native_lsp = {
+ enabled = true,
+ underlines = {
+ errors = { "undercurl" },
+ hints = { "undercurl" },
+ warnings = { "undercurl" },
+ information = { "undercurl" },
+ },
+ },
+ navic = { enabled = true, custom_bg = "lualine" },
+ neotest = true,
+ neotree = true,
+ noice = true,
+ notify = true,
+ semantic_tokens = true,
+ telescope = true,
+ treesitter = true,
+ treesitter_context = true,
+ which_key = true,
+ },
+ },
+ config = function()
+ require("catppuccin").setup({
+ transparent_background = true, -- disables setting the background color.
+ styles = { -- Handles the styles of general hi groups (see `:h highlight-args`):
+ comments = { "italic" }, -- Change the style of comments
+ conditionals = {},
+ loops = {},
+ functions = {},
+ keywords = { "bold" },
+ strings = {},
+ variables = {},
+ numbers = {},
+ booleans = {},
+ properties = {},
+ types = {},
+ operators = {},
+ },
+ color_overrides = {
+ mocha = {
+ rosewater = "#f5e0dc",
+ flamingo = "#f2cdcd",
+ pink = "#f5c2e7",
+ mauve = "#cba6f7",
+ red = "#f38ba8",
+ maroon = "#eba0ac",
+ peach = "#fab387",
+ yellow = "#f9e2af",
+ green = "#a6e3a1",
+ teal = "#94e2d5",
+ sky = "#89dceb",
+ sapphire = "#74c7ec",
+ blue = "#89b4fa",
+ lavender = "#b4befe",
+ text = "#cdd6f4",
+ subtext1 = "#bac2de",
+ subtext0 = "#a6adc8",
+ overlay2 = "#9399b2",
+ overlay1 = "#7f849c",
+ overlay0 = "#696D86",
+ surface2 = "#585b70",
+ surface1 = "#45475a",
+ surface0 = "#232728",
+ base = "#282828",
+ mantle = "#181825",
+ crust = "#11111b",
+ },
+ },
+ })
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/cmp.lua b/ar/.config/LazyVim/lua/plugins/cmp.lua
new file mode 100644
index 0000000..3121e90
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/cmp.lua
@@ -0,0 +1,50 @@
+return {
+ {
+ "L3MON4D3/LuaSnip",
+ keys = function()
+ return {}
+ end,
+ },
+ {
+ "hrsh7th/nvim-cmp",
+ dependencies = {
+ "hrsh7th/cmp-emoji",
+ },
+ ---@param opts cmp.ConfigSchema
+ opts = function(_, opts)
+ local has_words_before = function()
+ unpack = unpack or table.unpack
+ local line, col = unpack(vim.api.nvim_win_get_cursor(0))
+ return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
+ end
+
+ local luasnip = require("luasnip")
+ local cmp = require("cmp")
+
+ opts.mapping = vim.tbl_extend("force", opts.mapping, {
+ ["<Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ -- You could replace the expand_or_jumpable() calls with expand_or_locally_jumpable()
+ -- this way you will only jump inside the snippet region
+ elseif luasnip.expand_or_jumpable() then
+ luasnip.expand_or_jump()
+ elseif has_words_before() then
+ cmp.complete()
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ })
+ end,
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/colorscheme.lua b/ar/.config/LazyVim/lua/plugins/colorscheme.lua
new file mode 100644
index 0000000..6acb3d3
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/colorscheme.lua
@@ -0,0 +1,11 @@
+return {
+ { "catppuccin/nvim", name = "catppuccin", opts = { transparent_background = true }, priority = 1000 },
+ {
+ "LazyVim/LazyVim",
+ opts = {
+ -- colorscheme = "tokyonight",
+ colorscheme = "catppuccin",
+ -- colorscheme = "gruvbox",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/comment.lua b/ar/.config/LazyVim/lua/plugins/comment.lua
new file mode 100644
index 0000000..1979d56
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/comment.lua
@@ -0,0 +1,23 @@
+return {
+ "numToStr/Comment.nvim",
+ lazy = "false",
+ keys = {
+ {
+ mode = "n",
+ "<leader>/",
+ function()
+ require("Comment.api").toggle.linewise.current()
+ end,
+ desc = "Toggle comment",
+ },
+ {
+ mode = "v",
+ "<leader>/",
+ "<ESC><cmd>lua require('Comment.api').toggle.linewise(vim.fn.visualmode())<CR>",
+ desc = "Toggle comment",
+ },
+ },
+ config = function(_, opts)
+ require("Comment").setup(opts)
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/dadbod.lua b/ar/.config/LazyVim/lua/plugins/dadbod.lua
new file mode 100644
index 0000000..b3e8275
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/dadbod.lua
@@ -0,0 +1,25 @@
+return {
+ "kristijanhusak/vim-dadbod-ui",
+ dependencies = {
+ { "tpope/vim-dadbod", lazy = true },
+ { "kristijanhusak/vim-dadbod-completion", ft = { "sql", "mysql", "plsql" }, lazy = true },
+ },
+ cmd = {
+ "DBUI",
+ "DBUIToggle",
+ "DBUIAddConnection",
+ "DBUIFindBuffer",
+ },
+ init = function()
+ -- Your DBUI configuration
+ vim.g.db_ui_use_nerd_fonts = 1
+ end,
+ keys = {
+ { "<leader>ddb", "<cmd>DBUI<cr>", desc = "DB UI" },
+ { "<leader>ddu", "<cmd>DBUIToggle<cr>", desc = "Toggle DB UI" },
+ { "<leader>dda", "<cmd>DBUIAddConnection<cr>", desc = "Add Connection" },
+ { "<leader>ddf", "<cmd>DBUIFindBuffer<cr>", desc = "Find buffer" },
+ { "<leader>ddr", "<cmd>DBUIRenameBuffer<cr>", desc = "Rename buffer" },
+ { "<leader>ddl", "<cmd>DBUILastQueryInfo<cr>", desc = "Last query info" },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/dashboard-nvim.lua b/ar/.config/LazyVim/lua/plugins/dashboard-nvim.lua
new file mode 100644
index 0000000..8a1ffd6
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/dashboard-nvim.lua
@@ -0,0 +1,68 @@
+local Util = require("lazyvim.util")
+return {
+ "nvimdev/dashboard-nvim",
+ opts = function(_, opts)
+ local logo = [[
+ ┌──────────────────────────────────────────────────────┐
+ │ │
+ │ ███╗ ██╗███████╗ ██████╗ ██╗ ██╗██╗███╗ ███╗ │
+ │ ████╗ ██║██╔════╝██╔═══██╗██║ ██║██║████╗ ████║ │
+ │ ██╔██╗ ██║█████╗ ██║ ██║██║ ██║██║██╔████╔██║ │
+ │ ██║╚██╗██║██╔══╝ ██║ ██║╚██╗ ██╔╝██║██║╚██╔╝██║ │
+ │ ██║ ╚████║███████╗╚██████╔╝ ╚████╔╝ ██║██║ ╚═╝ ██║ │
+ │ ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ │
+ └──────────────────────────────────────────────────────┘
+ ]]
+
+ logo = string.rep("\n", 5) .. logo .. "\n"
+
+ local opts = {
+ theme = "doom",
+ hide = {
+ -- this is taken care of by lualine
+ -- enabling this messes up the actual laststatus setting after loading a file
+ statusline = false,
+ },
+
+ config = {
+ header = vim.split(logo, "\n"),
+ -- stylua: ignore
+ center = {
+ { action = "ene | startinsert", desc = " New file", icon = " ", key = "n" },
+ { action = "Telescope oldfiles", desc = " Recent files", icon = " ", key = "r" },
+ { action = "Telescope projects", desc = " Projects", icon = " ", key = "p" },
+ { action = Util.telescope("files"), desc = " Find file", icon = " ", key = "f" },
+ { action = "Telescope live_grep", desc = " Find word", icon = " ", key = "g" },
+ { action = [[lua require("lazyvim.util").telescope.config_files()()]], desc = " Config", icon = " ", key = "c" },
+ { action = 'lua require("persistence").load()', desc = " Restore Session", icon = " ", key = "s" },
+ { action = "LazyExtras", desc = " Lazy Extras", icon = " ", key = "e" },
+ { action = "Lazy", desc = " Lazy", icon = "󰒲 ", key = "l" },
+ { action = "Mason", desc = " Mason", icon = "◍ ", key = "m" },
+ { action = "qa", desc = " Quit", icon = " ", key = "q" },
+ },
+ footer = function()
+ local stats = require("lazy").stats()
+ local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100)
+ return { "⚡ Neovim loaded " .. stats.loaded .. "/" .. stats.count .. " plugins in " .. ms .. "ms" }
+ end,
+ },
+ }
+
+ for _, button in ipairs(opts.config.center) do
+ button.desc = button.desc .. string.rep(" ", 43 - #button.desc)
+ button.key_format = " %s"
+ end
+
+ -- close Lazy and re-open when the dashboard is ready
+ if vim.o.filetype == "lazy" then
+ vim.cmd.close()
+ vim.api.nvim_create_autocmd("User", {
+ pattern = "DashboardLoaded",
+ callback = function()
+ require("lazy").show()
+ end,
+ })
+ end
+ return opts
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/example.lua b/ar/.config/LazyVim/lua/plugins/example.lua
new file mode 100644
index 0000000..f84ebdc
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/example.lua
@@ -0,0 +1,265 @@
+-- since this is just an example spec, don't actually load anything here and return an empty spec
+-- stylua: ignore
+if true then return {} end
+
+-- every spec file under the "plugins" directory will be loaded automatically by lazy.nvim
+--
+-- In your plugin files, you can:
+-- * add extra plugins
+-- * disable/enabled LazyVim plugins
+-- * override the configuration of LazyVim plugins
+return {
+ -- add gruvbox
+ { "ellisonleao/gruvbox.nvim" },
+
+ -- Configure LazyVim to load gruvbox
+ {
+ "LazyVim/LazyVim",
+ opts = {
+ colorscheme = "gruvbox",
+ },
+ },
+
+ -- change trouble config
+ {
+ "folke/trouble.nvim",
+ -- opts will be merged with the parent spec
+ opts = { use_diagnostic_signs = true },
+ },
+
+ -- disable trouble
+ { "folke/trouble.nvim", enabled = false },
+
+ -- add symbols-outline
+ {
+ "simrat39/symbols-outline.nvim",
+ cmd = "SymbolsOutline",
+ keys = { { "<leader>cs", "<cmd>SymbolsOutline<cr>", desc = "Symbols Outline" } },
+ config = true,
+ },
+
+ -- override nvim-cmp and add cmp-emoji
+ {
+ "hrsh7th/nvim-cmp",
+ dependencies = { "hrsh7th/cmp-emoji" },
+ ---@param opts cmp.ConfigSchema
+ opts = function(_, opts)
+ table.insert(opts.sources, { name = "emoji" })
+ end,
+ },
+
+ -- change some telescope options and a keymap to browse plugin files
+ {
+ "nvim-telescope/telescope.nvim",
+ keys = {
+ -- add a keymap to browse plugin files
+ -- stylua: ignore
+ {
+ "<leader>fp",
+ function() require("telescope.builtin").find_files({ cwd = require("lazy.core.config").options.root }) end,
+ desc = "Find Plugin File",
+ },
+ },
+ -- change some options
+ opts = {
+ defaults = {
+ layout_strategy = "horizontal",
+ layout_config = { prompt_position = "top" },
+ sorting_strategy = "ascending",
+ winblend = 0,
+ },
+ },
+ },
+
+ -- add telescope-fzf-native
+ {
+ "telescope.nvim",
+ dependencies = {
+ "nvim-telescope/telescope-fzf-native.nvim",
+ build = "make",
+ config = function()
+ require("telescope").load_extension("fzf")
+ end,
+ },
+ },
+
+ -- add pyright to lspconfig
+ {
+ "neovim/nvim-lspconfig",
+ ---@class PluginLspOpts
+ opts = {
+ ---@type lspconfig.options
+ servers = {
+ -- pyright will be automatically installed with mason and loaded with lspconfig
+ pyright = {},
+ },
+ },
+ },
+
+ -- add tsserver and setup with typescript.nvim instead of lspconfig
+ {
+ "neovim/nvim-lspconfig",
+ dependencies = {
+ "jose-elias-alvarez/typescript.nvim",
+ init = function()
+ require("lazyvim.util").on_attach(function(_, buffer)
+ -- stylua: ignore
+ vim.keymap.set( "n", "<leader>co", "TypescriptOrganizeImports", { buffer = buffer, desc = "Organize Imports" })
+ vim.keymap.set("n", "<leader>cR", "TypescriptRenameFile", { desc = "Rename File", buffer = buffer })
+ end)
+ end,
+ },
+ ---@class PluginLspOpts
+ opts = {
+ ---@type lspconfig.options
+ servers = {
+ -- tsserver will be automatically installed with mason and loaded with lspconfig
+ tsserver = {},
+ },
+ -- you can do any additional lsp server setup here
+ -- return true if you don't want this server to be setup with lspconfig
+ ---@type table<string, fun(server:string, opts:_.lspconfig.options):boolean?>
+ setup = {
+ -- example to setup with typescript.nvim
+ tsserver = function(_, opts)
+ require("typescript").setup({ server = opts })
+ return true
+ end,
+ -- Specify * to use this function as a fallback for any server
+ -- ["*"] = function(server, opts) end,
+ },
+ },
+ },
+
+ -- for typescript, LazyVim also includes extra specs to properly setup lspconfig,
+ -- treesitter, mason and typescript.nvim. So instead of the above, you can use:
+ { import = "lazyvim.plugins.extras.lang.typescript" },
+
+ -- add more treesitter parsers
+ {
+ "nvim-treesitter/nvim-treesitter",
+ opts = {
+ ensure_installed = {
+ "bash",
+ "html",
+ "javascript",
+ "json",
+ "lua",
+ "markdown",
+ "markdown_inline",
+ "python",
+ "query",
+ "regex",
+ "tsx",
+ "typescript",
+ "vim",
+ "yaml",
+ },
+ },
+ },
+
+ -- since `vim.tbl_deep_extend`, can only merge tables and not lists, the code above
+ -- would overwrite `ensure_installed` with the new value.
+ -- If you'd rather extend the default config, use the code below instead:
+ {
+ "nvim-treesitter/nvim-treesitter",
+ opts = function(_, opts)
+ -- add tsx and treesitter
+ vim.list_extend(opts.ensure_installed, {
+ "tsx",
+ "typescript",
+ })
+ end,
+ },
+
+ -- the opts function can also be used to change the default opts:
+ {
+ "nvim-lualine/lualine.nvim",
+ event = "VeryLazy",
+ opts = function(_, opts)
+ table.insert(opts.sections.lualine_x, "😄")
+ end,
+ },
+
+ -- or you can return new options to override all the defaults
+ {
+ "nvim-lualine/lualine.nvim",
+ event = "VeryLazy",
+ opts = function()
+ return {
+ --[[add your custom lualine config here]]
+ }
+ end,
+ },
+
+ -- use mini.starter instead of alpha
+ { import = "lazyvim.plugins.extras.ui.mini-starter" },
+
+ -- add jsonls and schemastore packages, and setup treesitter for json, json5 and jsonc
+ { import = "lazyvim.plugins.extras.lang.json" },
+
+ -- add any tools you want to have installed below
+ {
+ "williamboman/mason.nvim",
+ opts = {
+ ensure_installed = {
+ "stylua",
+ "shellcheck",
+ "shfmt",
+ "flake8",
+ },
+ },
+ },
+
+ -- Use <tab> for completion and snippets (supertab)
+ -- first: disable default <tab> and <s-tab> behavior in LuaSnip
+ {
+ "L3MON4D3/LuaSnip",
+ keys = function()
+ return {}
+ end,
+ },
+ -- then: setup supertab in cmp
+ {
+ "hrsh7th/nvim-cmp",
+ dependencies = {
+ "hrsh7th/cmp-emoji",
+ },
+ ---@param opts cmp.ConfigSchema
+ opts = function(_, opts)
+ local has_words_before = function()
+ unpack = unpack or table.unpack
+ local line, col = unpack(vim.api.nvim_win_get_cursor(0))
+ return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
+ end
+
+ local luasnip = require("luasnip")
+ local cmp = require("cmp")
+
+ opts.mapping = vim.tbl_extend("force", opts.mapping, {
+ ["<Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ -- You could replace the expand_or_jumpable() calls with expand_or_locally_jumpable()
+ -- this way you will only jump inside the snippet region
+ elseif luasnip.expand_or_jumpable() then
+ luasnip.expand_or_jump()
+ elseif has_words_before() then
+ cmp.complete()
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ })
+ end,
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/file-browser.lua b/ar/.config/LazyVim/lua/plugins/file-browser.lua
new file mode 100644
index 0000000..9a9ea2f
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/file-browser.lua
@@ -0,0 +1,15 @@
+return {
+ "nvim-telescope/telescope-file-browser.nvim",
+ dependencies = { "nvim-telescope/telescope.nvim", "nvim-lua/plenary.nvim" },
+ keys = {
+ {
+ mode = "n",
+ "<leader>sB",
+ ":Telescope file_browser path=%:p:h=%:p:h<cr>",
+ desc = "Browse Files",
+ },
+ },
+ config = function()
+ require("telescope").load_extension("file_browser")
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/glow.lua b/ar/.config/LazyVim/lua/plugins/glow.lua
new file mode 100644
index 0000000..e4702d2
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/glow.lua
@@ -0,0 +1,8 @@
+return {
+ "ellisonleao/glow.nvim",
+ cmd = "Glow",
+ config = true,
+ keys = {
+ { mode = "n", "<leader>mf", "<cmd>Glow<cr>", desc = "Markdown Preview float" },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/goyo.lua b/ar/.config/LazyVim/lua/plugins/goyo.lua
new file mode 100644
index 0000000..9d0f860
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/goyo.lua
@@ -0,0 +1,53 @@
+vim.g.is_goyo_active = false
+
+vim.g.goyo_enter = function()
+ if vim.fn.executable("tmux") == 1 and vim.fn.strlen(vim.env.TMUX) > 0 then
+ vim.fn.system("tmux set status off")
+ if not string.find(vim.fn.system("tmux list-panes -F '#F'"), "Z") then
+ vim.fn.system("tmux resize-pane -Z")
+ end
+ end
+
+ vim.g.default_colorscheme = vim.g.colors_name or "default"
+ vim.o.background = "light"
+ vim.wo.linebreak = true
+ vim.wo.wrap = true
+ vim.bo.textwidth = 0
+ vim.bo.wrapmargin = 0
+ vim.cmd("Goyo 80x85%")
+ vim.cmd("colorscheme seoul256")
+ vim.g.is_goyo_active = true
+end
+
+vim.g.goyo_leave = function()
+ if vim.fn.executable("tmux") == 1 and vim.fn.strlen(vim.env.TMUX) > 0 then
+ vim.fn.system("tmux set status on")
+ if string.find(vim.fn.system("tmux list-panes -F '#F'"), "Z") then
+ vim.fn.system("tmux resize-pane -Z")
+ end
+ end
+ vim.cmd("Goyo!")
+ vim.cmd("colorscheme " .. vim.g.default_colorscheme)
+ vim.g.is_goyo_active = false
+end
+
+vim.g.toggle_goyo = function()
+ if vim.g.is_goyo_active then
+ vim.g.goyo_leave()
+ else
+ vim.g.goyo_enter()
+ end
+end
+
+return {
+ "junegunn/goyo.vim",
+ cmd = "Goyo",
+ keys = {
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>gy",
+ "<cmd>lua vim.g.toggle_goyo()<CR>",
+ { noremap = true, silent = true, desc = "Toggle Goyo" }
+ ),
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/harpoon.lua b/ar/.config/LazyVim/lua/plugins/harpoon.lua
new file mode 100644
index 0000000..a156e1d
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/harpoon.lua
@@ -0,0 +1,19 @@
+return {
+ "ThePrimeagen/harpoon",
+ branch = "harpoon2",
+ opts = {
+ menu = {
+ width = vim.api.nvim_win_get_width(0) - 4,
+ },
+ },
+ keys = {
+ {
+ "<C-e>",
+ function()
+ local harpoon = require("harpoon")
+ harpoon.ui:toggle_quick_menu(harpoon:list())
+ end,
+ desc = "Harpoon quick menu",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/impatient.lua b/ar/.config/LazyVim/lua/plugins/impatient.lua
new file mode 100644
index 0000000..d0877c9
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/impatient.lua
@@ -0,0 +1,6 @@
+return {
+ "lewis6991/impatient.nvim",
+ config = function()
+ require("impatient")
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/jukit.lua b/ar/.config/LazyVim/lua/plugins/jukit.lua
new file mode 100644
index 0000000..f5ad24f
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/jukit.lua
@@ -0,0 +1,79 @@
+return {
+ -- "luk400/vim-jukit",
+ -- lazy = false,
+ -- keys = {
+ -- { "<leader>os", false },
+ -- { "<leader>ts", false },
+ -- { "<leader>hs", false },
+ -- { "<leader>ohs", false },
+ -- { "<leader>hd", false },
+ -- { "<leader>od", false },
+ -- { "<leader>ohd", false },
+ -- { "<leader>so", false },
+ -- { "<leader>j", false },
+ -- { "<leader>k", false },
+ -- { "<leader>ah", false },
+ -- { "<leader>sl", false },
+ -- { "<space>", false },
+ -- { "<cr>", false },
+ -- { "<cr>", false },
+ -- { "<leader>cc", false },
+ -- { "<leader>all", false },
+ -- { "<leader>co", false },
+ -- { "<leader>cO", false },
+ -- { "<leader>ct", false },
+ -- { "<leader>cT", false },
+ -- { "<leader>cd", false },
+ -- { "<leader>cs", false },
+ -- { "<leader>cM", false },
+ -- { "<leader>cm", false },
+ -- { "<leader>ck", false },
+ -- { "<leader>cj", false },
+ -- { "<leader>J", false },
+ -- { "<leader>K", false },
+ -- { "<leader>ddo", false },
+ -- { "<leader>dda", false },
+ -- { "<leader>np", false },
+ -- { "<leader>ht", false },
+ -- { "<leader>rht", false },
+ -- { "<leader>pd", false },
+ -- { "<leader>rpd", false },
+ --
+ -- { "<localleader>os", ":call jukit#splits#output()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ts", ":call jukit#splits#term()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>hs", ":call jukit#splits#history()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ohs", ":call jukit#splits#output_and_history()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>hd", ":call jukit#splits#close_history()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>od", ":call jukit#splits#close_output_split()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ohd", ":call jukit#splits#close_output_and_history(1)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>so", ":call jukit#splits#show_last_cell_output(1)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>j", ":call jukit#splits#out_hist_scroll(1)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>k", ":call jukit#splits#out_hist_scroll(0)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ah", ":call jukit#splits#toggle_auto_hist()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>sl", ":call jukit#layouts#set_layout()<cr>", desc = "", mode = "n" },
+ -- { "<localleader><space>", ":call jukit#send#section(0)<cr>", desc = "", mode = "n" },
+ -- { "<localleader><cr>", ":call jukit#send#line()<cr>", desc = "", mode = "n" },
+ -- { "<localleader><cr>", ":<C-U>call jukit#send#selection()<cr>", desc = "", mode = "v" },
+ -- { "<localleader>cc", ":call jukit#send#until_current_section()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>all", ":call jukit#send#all()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>co", ":call jukit#cells#create_below(0)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cO", ":call jukit#cells#create_above(0)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ct", ":call jukit#cells#create_below(1)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cT", ":call jukit#cells#create_above(1)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cd", ":call jukit#cells#delete()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cs", ":call jukit#cells#split()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cM", ":call jukit#cells#merge_above()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cm", ":call jukit#cells#merge_below()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ck", ":call jukit#cells#move_up()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>cj", ":call jukit#cells#move_down()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>J", ":call jukit#cells#jump_to_next_cell()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>K", ":call jukit#cells#jump_to_previous_cell()<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ddo", ":call jukit#cells#delete_outputs(0)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>dda", ":call jukit#cells#delete_outputs(1)<cr>", desc = "", mode = "n" },
+ -- { "<localleader>np", ":call jukit#convert#notebook_convert('jupyter-notebook')<cr>", desc = "", mode = "n" },
+ -- { "<localleader>ht", ":call jukit#convert#save_nb_to_file(0,1,'html')<cr>", desc = "", mode = "n" },
+ -- { "<localleader>rht", ":call jukit#convert#save_nb_to_file(1,1,'html')<cr>", desc = "", mode = "n" },
+ -- { "<localleader>pd", ":call jukit#convert#save_nb_to_file(0,1,'pdf')<cr>", desc = "", mode = "n" },
+ -- { "<localleader>rpd", ":call jukit#convert#save_nb_to_file(1,1,'pdf')<cr>", desc = "", mode = "n" },
+ -- },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/jupyter.lua b/ar/.config/LazyVim/lua/plugins/jupyter.lua
new file mode 100644
index 0000000..0cb684b
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/jupyter.lua
@@ -0,0 +1,345 @@
+local function get_commenter()
+ local commenter = { python = "# ", lua = "-- ", julia = "# ", fennel = ";; ", scala = "// ", r = "# " }
+ local bufnr = vim.api.nvim_get_current_buf()
+ local ft = vim.api.nvim_buf_get_option(bufnr, "filetype")
+ if ft == nil or ft == "" then
+ return commenter["python"]
+ elseif commenter[ft] == nil then
+ return commenter["python"]
+ end
+
+ return commenter[ft]
+end
+
+local CELL_MARKER = get_commenter() .. "%%"
+vim.api.nvim_set_hl(0, "CellMarkerHl", { default = true, bg = "#c5c5c5", fg = "#111111" })
+
+local function miniai_spec(mode)
+ local start_line = vim.fn.search("^" .. CELL_MARKER, "bcnW")
+
+ if start_line == 0 then
+ start_line = 1
+ else
+ if mode == "i" then
+ start_line = start_line + 1
+ end
+ end
+
+ local end_line = vim.fn.search("^" .. CELL_MARKER, "nW") - 1
+ if end_line == -1 then
+ end_line = vim.fn.line("$")
+ end
+
+ local last_col = math.max(vim.fn.getline(end_line):len(), 1)
+
+ local from = { line = start_line, col = 1 }
+ local to = { line = end_line, col = last_col }
+
+ return { from = from, to = to }
+end
+
+local function show_cell_markers()
+ require("mini.hipatterns").enable(0, {
+ highlighters = {
+ marker = { pattern = "^" .. get_commenter() .. "%%%%", group = "CellMarkerHl" },
+ },
+ })
+end
+
+local function select_cell()
+ local bufnr = vim.api.nvim_get_current_buf()
+ local current_row = vim.api.nvim_win_get_cursor(0)[1]
+ local current_col = vim.api.nvim_win_get_cursor(0)[2]
+
+ local start_line = nil
+ local end_line = nil
+
+ for line = current_row, 1, -1 do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:find("^" .. CELL_MARKER) then
+ start_line = line
+ break
+ end
+ end
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+ for line = current_row + 1, line_count do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:find("^" .. CELL_MARKER) then
+ end_line = line
+ break
+ end
+ end
+
+ if not start_line then
+ start_line = 1
+ end
+ if not end_line then
+ end_line = line_count
+ end
+ return current_row, current_col, start_line, end_line
+end
+
+local function execute_cell()
+ local current_row, current_col, start_line, end_line = select_cell()
+ if start_line and end_line then
+ vim.fn.setpos("'<", { 0, start_line + 1, 0, 0 })
+ vim.fn.setpos("'>", { 0, end_line - 1, 0, 0 })
+ require("iron.core").visual_send()
+ vim.api.nvim_win_set_cursor(0, { current_row, current_col })
+ end
+end
+
+local function delete_cell()
+ local _, _, start_line, end_line = select_cell()
+ if start_line and end_line then
+ local rows_to_select = end_line - start_line - 1
+ vim.api.nvim_win_set_cursor(0, { start_line, 0 })
+ vim.cmd("normal!V " .. rows_to_select .. "j")
+ vim.cmd("normal!d")
+ vim.cmd("normal!k")
+ end
+end
+
+local function navigate_cell(up)
+ local is_up = up or false
+ local _, _, start_line, end_line = select_cell()
+ if is_up and start_line ~= 1 then
+ vim.api.nvim_win_set_cursor(0, { start_line - 1, 0 })
+ elseif end_line then
+ local bufnr = vim.api.nvim_get_current_buf()
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+ if end_line ~= line_count then
+ vim.api.nvim_win_set_cursor(0, { end_line + 1, 0 })
+ _, _, start_line, end_line = select_cell()
+ vim.api.nvim_win_set_cursor(0, { end_line - 1, 0 })
+ end
+ end
+end
+
+local function move_cell(dir)
+ local search_res
+ local result
+ if dir == "d" then
+ search_res = vim.fn.search("^" .. CELL_MARKER, "W")
+ if search_res == 0 then
+ result = "last"
+ end
+ else
+ search_res = vim.fn.search("^" .. CELL_MARKER, "bW")
+ if search_res == 0 then
+ result = "first"
+ vim.api.nvim_win_set_cursor(0, { 1, 0 })
+ end
+ end
+
+ return result
+end
+
+local function insert_cell_before(content)
+ content = content or CELL_MARKER
+ local cell_object = miniai_spec("a")
+ vim.api.nvim_buf_set_lines(0, cell_object.from.line - 1, cell_object.from.line - 1, false, { content, "" })
+ move_cell("u")
+end
+
+local function insert_cell_after(content)
+ content = content or CELL_MARKER
+ vim.print(content)
+ local cell_object = miniai_spec("a")
+ vim.api.nvim_buf_set_lines(0, cell_object.to.line, cell_object.to.line, false, { content, "" })
+ move_cell("d")
+end
+
+local function insert_markdown_cell()
+ insert_cell_after(CELL_MARKER .. " [markdown]")
+end
+
+local function repl_menu()
+ local cmd = require("hydra.keymap-util").cmd
+
+ local hint = [[
+ ^
+ _a_: Insert Cell After
+ _b_: Insert Cell Before
+ _e_: Execute Cell
+ _j_: Next Cell
+ _k_: Previous Cell
+ _m_: Insert Markdown Cell
+ _x_: Delete Cell
+ ^
+ _s_: Send Motion
+ _l_: Send Line
+ _t_: Send Until Cursor
+ _f_: Send File
+ ^
+ _R_: Show REPL
+ _C_: Close REPL
+ _S_: Restart REPL
+ _F_: Focus
+ _H_: Hide
+ ^
+ _c_: Clear
+ _L_: Clear Highlight
+ _<CR>_: ENTER
+ _I_: Interrupt
+ ^
+ ^ ^ _q_: Quit
+]]
+
+ return {
+ name = "REPL",
+ hint = hint,
+ config = {
+ color = "pink",
+ invoke_on_body = true,
+ hint = {
+ border = "rounded",
+ position = "bottom-middle",
+ },
+ },
+ mode = "n",
+ body = "<A-n>",
+ -- stylua: ignore
+ heads = {
+ { "a", insert_cell_after, desc = "Insert Cell After", },
+ { "b", insert_cell_before, desc = "Insert Cell Before", },
+ { "e", execute_cell, desc = "Execute Cell", },
+ { "j", navigate_cell , desc = "Next Cell", },
+ { "k", function() navigate_cell(true) end, desc = "Previous Cell", },
+ { "m", insert_markdown_cell, desc = "Insert Markdown Cell", },
+ { "x", delete_cell, desc = "Delete Cell", },
+ { "s", function() require("iron.core").run_motion("send_motion") end, desc = "Send Motion" },
+ { "l", function() require("iron.core").send_line() end, desc = "Send Line" },
+ { "t", function() require("iron.core").send_until_cursor() end, desc = "Send Until Cursor" },
+ { "f", function() require("iron.core").send_file() end, desc = "Send File" },
+ { "L", function() require("iron.marks").clear_hl() end, mode = {"v"}, desc = "Clear Highlight" },
+ { "<CR>", function() require("iron.core").send(nil, string.char(13)) end, desc = "ENTER" },
+ { "I", function() require("iron.core").send(nil, string.char(03)) end, desc = "Interrupt" },
+ { "C", function() require("iron.core").close_repl() end, desc = "Close REPL" },
+ { "c", function() require("iron.core").send(nil, string.char(12)) end, desc = "Clear" },
+ { "R", cmd("IronRepl"), desc = "REPL" },
+ { "S", cmd("IronRestart"), desc = "Restart" },
+ { "F", cmd("IronFocus"), desc = "Focus" },
+ { "H", cmd("IronHide"), desc = "Hide" },
+ { "q", nil, { exit = true, nowait = true, desc = "Exit" } },
+ },
+ }
+end
+
+return {
+ {
+ "goerz/jupytext.vim",
+ build = "pip install jupytext",
+ event = "VeryLazy",
+ dependencies = { "neovim/nvim-lspconfig" },
+ opts = {},
+ config = function()
+ -- The destination format: 'ipynb', 'markdown' or 'script', or a file extension: 'md', 'Rmd', 'jl', 'py', 'R', ..., 'auto' (script
+ -- extension matching the notebook language), or a combination of an extension and a format name, e.g. md:markdown, md:pandoc,
+ -- md:myst or py:percent, py:light, py:nomarker, py:hydrogen, py:sphinx. The default format for scripts is the 'light' format,
+ -- which uses few cell markers (none when possible). Alternatively, a format compatible with many editors is the 'percent' format,
+ -- which uses '# %%' as cell markers. The main formats (markdown, light, percent) preserve notebooks and text documents in a
+ -- roundtrip. Use the --test and and --test-strict commands to test the roundtrip on your files. Read more about the available
+ -- formats at https://jupytext.readthedocs.io/en/latest/formats.html (default: None)
+ vim.g.jupytext_fmt = "py:percent"
+
+ -- Autocmd to set cell markers
+ vim.api.nvim_create_autocmd({ "BufEnter" }, { -- "BufWriteCmd"
+ group = vim.api.nvim_create_augroup("au_show_cell_markers", { clear = true }),
+ pattern = { "*.py", "*.r", "*.ipynb", "*.jl", "*.scala", "*.lua", "*.fnl" },
+ callback = function(event)
+ show_cell_markers()
+ end,
+ })
+ end,
+ },
+ {
+ "Vigemus/iron.nvim",
+ event = "VeryLazy",
+ opts = function()
+ return {
+ config = {
+ -- Whether a repl should be discarded or not
+ scratch_repl = true,
+ -- Your repl definitions come here
+
+ repl_definition = {
+ python = require("iron.fts.python").ipython,
+ scala = require("iron.fts.scala").scala,
+ },
+ -- How the repl window will be displayed
+ -- See below for more information
+ repl_open_cmd = require("iron.view").right("50%"),
+ },
+ -- If the highliht is on, you can change how it looks
+ -- For the available options, check nvim_set_hl
+ highlight = {
+ italic = true,
+ },
+ ignore_blank_lines = true, -- ignore blank lines when sending visual select lines
+ }
+ end,
+ -- stylua: ignore
+ keys = {
+ { "<leader>i", function() end, mode = {"n", "x"}, desc = "+REPL" },
+ { "<leader>im", function() end, mode = {"n", "x"}, desc = "+Mark" },
+ { "<leader>ire>", execute_cell, desc = "Execute Cell" },
+ { "<leader>irx", delete_cell, desc = "Delete Cell" },
+ { "<leader>ir]", navigate_cell, desc = "Next Cell" },
+ { "<leader>ir[", function() navigate_cell(true) end, desc = "Previous Cell" },
+ { "<leader>is", function() require("iron.core").run_motion("send_motion") end, desc = "Send Motion" },
+ { "<leader>is", function() require("iron.core").visual_send() end, mode = {"v"}, desc = "Send" },
+ { "<leader>il", function() require("iron.core").send_line() end, desc = "Send Line" },
+ { "<leader>it", function() require("iron.core").send_until_cursor() end, desc = "Send Until Cursor" },
+ { "<leader>if", function() require("iron.core").send_file() end, desc = "Send File" },
+ { "<leader>iL", function() require("iron.marks").clear_hl() end, mode = {"v"}, desc = "Clear Highlight" },
+ { "<leader>i<cr>", function() require("iron.core").send(nil, string.char(13)) end, desc = "ENTER" },
+ { "<leader>iI", function() require("iron.core").send(nil, string.char(03)) end, desc = "Interrupt" },
+ { "<leader>iC", function() require("iron.core").close_repl() end, desc = "Close REPL" },
+ { "<leader>ic", function() require("iron.core").send(nil, string.char(12)) end, desc = "Clear" },
+ { "<leader>ims", function() require("iron.core").send_mark() end, desc = "Send Mark" },
+ { "<leader>imm", function() require("iron.core").run_motion("mark_motion") end, desc = "Mark Motion" },
+ { "<leader>imv", function() require("iron.core").mark_visual() end, mode = {"v"}, desc = "Mark Visual" },
+ { "<leader>imr", function() require("iron.marks").drop_last() end, desc = "Remove Mark" },
+ { "<leader>iR", "<cmd>IronRepl<cr>", desc = "REPL" },
+ { "<leader>iS", "<cmd>IronRestart<cr>", desc = "Restart" },
+ { "<leader>iF", "<cmd>IronFocus<cr>", desc = "Focus" },
+ { "<leader>iH", "<cmd>IronHide<cr>", desc = "Hide" },
+ },
+ config = function(_, opts)
+ local iron = require("iron.core")
+ iron.setup(opts)
+ end,
+ },
+ {
+ "folke/which-key.nvim",
+ event = "VeryLazy",
+ opts = {
+ defaults = {
+ ["<leader>i"] = { name = "+REPL" },
+ ["<leader>im"] = { name = "+Mark" },
+ },
+ },
+ },
+ {
+ "anuvyklack/hydra.nvim",
+ event = { "VeryLazy" },
+ opts = {
+ specs = {
+ repl = repl_menu,
+ },
+ },
+ config = function(_, opts)
+ local hydra = require("hydra")
+ for s, _ in pairs(opts.specs) do
+ hydra(opts.specs[s]())
+ end
+ end,
+ },
+ {
+ "echasnovski/mini.ai",
+ opts = function(_, opts)
+ opts.custom_textobjects = vim.tbl_extend("force", opts.custom_textobjects, { h = miniai_spec })
+ end,
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/lspconfig.lua b/ar/.config/LazyVim/lua/plugins/lspconfig.lua
new file mode 100644
index 0000000..4d8db1c
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/lspconfig.lua
@@ -0,0 +1,77 @@
+return {
+ "neovim/nvim-lspconfig",
+ init = function()
+ local keys = require("lazyvim.plugins.lsp.keymaps").get()
+ -- change a keymap
+ -- keys[#keys + 1] = { "K", "<cmd>echo 'hello'<cr>" }
+ -- keys[#keys + 1] = { "<c-k>", mode = { "i" }, false }
+
+ -- disable a keymap
+ -- keys[#keys + 1] = { "K", false }
+ keys[#keys + 1] = { mode = { "i" }, "<c-k>", false }
+
+ -- add a keymap
+ -- keys[#keys + 1] = { "H", "<cmd>echo 'hello'<cr>" }
+ end,
+ opts = {
+ servers = {
+ -- Ensure mason installs the server
+ clangd = {
+ keys = {
+ { "<leader>cR", "<cmd>ClangdSwitchSourceHeader<cr>", desc = "Switch Source/Header (C/C++)" },
+ },
+ root_dir = function(fname)
+ return require("lspconfig.util").root_pattern(
+ "Makefile",
+ "configure.ac",
+ "configure.in",
+ "config.h.in",
+ "meson.build",
+ "meson_options.txt",
+ "build.ninja"
+ )(fname) or require("lspconfig.util").root_pattern("compile_commands.json", "compile_flags.txt")(
+ fname
+ ) or require("lspconfig.util").find_git_ancestor(fname)
+ end,
+ capabilities = {
+ offsetEncoding = { "utf-16" },
+ },
+ cmd = {
+ "clangd",
+ "--background-index",
+ "--clang-tidy",
+ "--header-insertion=iwyu",
+ "--completion-style=detailed",
+ "--function-arg-placeholders",
+ "--fallback-style=llvm",
+ },
+ init_options = {
+ usePlaceholders = true,
+ completeUnimported = true,
+ clangdFileStatus = true,
+ },
+ },
+ },
+ setup = {
+ clangd = function(_, opts)
+ local clangd_ext_opts = require("lazyvim.util").opts("clangd_extensions.nvim")
+ require("clangd_extensions").setup(vim.tbl_deep_extend("force", clangd_ext_opts or {}, { server = opts }))
+ return false
+ end,
+ },
+ },
+ keys = {
+ {
+ -- mode = "n",
+ "<leader>rr",
+ ":w | :TermExec cmd='compiler \"%\"' size=50 direction=tab go_back=0<cr>",
+ desc = "Run Code",
+ },
+ {
+ -- mode = "n",
+ "<leader>rd",
+ ":w | :TermExec cmd='compiler \"%\" -d' size=50 direction=tab go_back=0<cr>",
+ desc = "Debug Code",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/lualine.lua b/ar/.config/LazyVim/lua/plugins/lualine.lua
new file mode 100644
index 0000000..7c64086
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/lualine.lua
@@ -0,0 +1,105 @@
+return {
+ "nvim-lualine/lualine.nvim",
+ event = "VeryLazy",
+ init = function()
+ vim.g.lualine_laststatus = vim.o.laststatus
+ if vim.fn.argc(-1) > 0 then
+ -- set an empty statusline till lualine loads
+ vim.o.statusline = " "
+ else
+ -- hide the statusline on the starter page
+ vim.o.laststatus = 0
+ end
+ end,
+ opts = function()
+ -- PERF: we don't need this lualine require madness 🤷
+ local lualine_require = require("lualine_require")
+ lualine_require.require = require
+
+ local icons = require("lazyvim.config").icons
+
+ vim.o.laststatus = vim.g.lualine_laststatus
+
+ return {
+ options = {
+ theme = "auto",
+ component_separators = { left = " ", right = " " },
+ section_separators = { left = " ", right = " " },
+ globalstatus = true,
+ disabled_filetypes = { statusline = { "dashboard", "alpha", "starter" } },
+ },
+ sections = {
+ lualine_a = { "mode" },
+ lualine_b = { "branch" },
+
+ lualine_c = {
+ LazyVim.lualine.root_dir(),
+ {
+ "diagnostics",
+ symbols = {
+ error = icons.diagnostics.Error,
+ warn = icons.diagnostics.Warn,
+ info = icons.diagnostics.Info,
+ hint = icons.diagnostics.Hint,
+ },
+ },
+ { "filetype", icon_only = true, separator = "", padding = { left = 1, right = 0 } },
+ { LazyVim.lualine.pretty_path() },
+ },
+ lualine_x = {
+ -- stylua: ignore
+ {
+ function() return require("noice").api.status.command.get() end,
+ cond = function() return package.loaded["noice"] and require("noice").api.status.command.has() end,
+ color = LazyVim.ui.fg("Statement"),
+ },
+ -- stylua: ignore
+ {
+ function() return require("noice").api.status.mode.get() end,
+ cond = function() return package.loaded["noice"] and require("noice").api.status.mode.has() end,
+ color = LazyVim.ui.fg("Constant"),
+ },
+ -- stylua: ignore
+ {
+ function() return " " .. require("dap").status() end,
+ cond = function () return package.loaded["dap"] and require("dap").status() ~= "" end,
+ color = LazyVim.ui.fg("Debug"),
+ },
+ {
+ require("lazy.status").updates,
+ cond = require("lazy.status").has_updates,
+ color = LazyVim.ui.fg("Special"),
+ },
+ {
+ "diff",
+ symbols = {
+ added = icons.git.added,
+ modified = icons.git.modified,
+ removed = icons.git.removed,
+ },
+ source = function()
+ local gitsigns = vim.b.gitsigns_status_dict
+ if gitsigns then
+ return {
+ added = gitsigns.added,
+ modified = gitsigns.changed,
+ removed = gitsigns.removed,
+ }
+ end
+ end,
+ },
+ },
+ lualine_y = {
+ { "progress", separator = " ", padding = { left = 1, right = 0 } },
+ { "location", padding = { left = 0, right = 1 } },
+ },
+ lualine_z = {
+ function()
+ return " " .. os.date("%R")
+ end,
+ },
+ },
+ extensions = { "neo-tree", "lazy" },
+ }
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/magma.lua b/ar/.config/LazyVim/lua/plugins/magma.lua
new file mode 100644
index 0000000..f648762
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/magma.lua
@@ -0,0 +1,23 @@
+return {
+ "dccsillag/magma-nvim",
+ build = ":UpdateRemotePlugins",
+ keys = {
+ {
+ "<LocalLeader>r",
+ ":MagmaEvaluateOperator<CR>",
+ expr = true,
+ silent = true,
+ desc = "Evaluate Operator",
+ mode = "n",
+ },
+ { "<LocalLeader>rr", ":MagmaEvaluateLine<CR>", silent = true, desc = "Evaluate Line", mode = "n" },
+ { "<LocalLeader>rv", ":<C-u>MagmaEvaluateVisual<CR>", silent = true, desc = "Evaluate Visual", mode = "x" },
+ { "<LocalLeader>rc", ":MagmaReevaluateCell<CR>", silent = true, desc = "Reevaluate Cell", mode = "n" },
+ { "<LocalLeader>rd", ":MagmaDelete<CR>", silent = true, desc = "Delete", mode = "n" },
+ { "<LocalLeader>ro", ":MagmaShowOutput<CR>", silent = true, desc = "Show Output", mode = "n" },
+ { "<LocalLeader>ri", ":MagmaInit python3<CR>", silent = true, desc = "Init", mode = "n" },
+ -- let g:magma_automatically_open_output = v:false
+ -- let g:magma_image_provider = "ueberzug"
+ -- let g:magma_image_provider = "kitty"
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/markdown-preview.lua b/ar/.config/LazyVim/lua/plugins/markdown-preview.lua
new file mode 100644
index 0000000..328f2e9
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/markdown-preview.lua
@@ -0,0 +1,23 @@
+return {
+ "iamcco/markdown-preview.nvim",
+ cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
+ -- build = function()
+ -- vim.fn["mkdp#util#install"]()
+ -- end,
+ keys = {
+ {
+ "<leader>cp",
+ false,
+ },
+ {
+ mode = "n",
+ "<leader>mp",
+ ft = "markdown",
+ "<cmd>MarkdownPreviewToggle<cr>",
+ desc = "Markdown Preview",
+ },
+ },
+ -- config = function()
+ -- vim.cmd([[do FileType]])
+ -- end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/mason.lua b/ar/.config/LazyVim/lua/plugins/mason.lua
new file mode 100644
index 0000000..96e320d
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/mason.lua
@@ -0,0 +1,92 @@
+local options = {
+ ensure_installed = {
+ "black",
+ "clangd",
+ "codelldb",
+ "debugpy",
+ "docker-compose-language-service",
+ "dockerfile-language-server",
+ "eslint-lsp",
+ "hadolint",
+ "java-debug-adapter",
+ "java-test",
+ "jdtls",
+ "json-lsp",
+ "lua-language-server",
+ "markdownlint",
+ "marksman",
+ "prettier",
+ "pyright",
+ "ruff",
+ "shfmt",
+ "stylua",
+ "yaml-language-server",
+ },
+
+ max_concurrent_installers = 10,
+}
+
+return {
+ "williamboman/mason.nvim",
+ cmd = { "Mason", "MasonInstall", "MasonInstallAll", "MasonUpdate" },
+ opts = function()
+ return options
+ end,
+ build = ":MasonUpdate",
+ ---@param opts MasonSettings | {ensure_installed: string[]}
+ config = function(_, opts)
+ require("mason").setup(opts)
+ local mr = require("mason-registry")
+ mr:on("package:install:success", function()
+ vim.defer_fn(function()
+ -- trigger FileType event to possibly load this newly installed LSP server
+ require("lazy.core.handler.event").trigger({
+ event = "FileType",
+ buf = vim.api.nvim_get_current_buf(),
+ })
+ end, 100)
+ end)
+ local function ensure_installed()
+ for _, tool in ipairs(opts.ensure_installed) do
+ local p = mr.get_package(tool)
+ if not p:is_installed() then
+ p:install()
+ end
+ end
+ end
+ if mr.refresh then
+ mr.refresh(ensure_installed)
+ else
+ ensure_installed()
+ end
+ require("mason").setup(opts)
+ vim.api.nvim_create_user_command("MasonInstallAll", function()
+ if opts.ensure_installed and #opts.ensure_installed > 0 then
+ vim.cmd("MasonInstall " .. table.concat(opts.ensure_installed, " "))
+ end
+ end, {})
+
+ vim.g.mason_binaries_list = opts.ensure_installed
+ end,
+ keys = {
+ { "<leader>cm", false },
+ {
+ mode = "n",
+ "<leader>ms",
+ "<cmd>Mason<cr>",
+ desc = "Mason",
+ },
+ {
+ mode = "n",
+ "<leader>mu",
+ "<cmd>MasonUpdate<cr>",
+ desc = "Mason Update",
+ },
+ {
+ mode = "n",
+ "<leader>mi",
+ "<cmd>MasonInstallAll<cr>",
+ desc = "Mason Install All",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/neogen.lua b/ar/.config/LazyVim/lua/plugins/neogen.lua
new file mode 100644
index 0000000..0fbe48a
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/neogen.lua
@@ -0,0 +1,33 @@
+local opts = { noremap = true, silent = true }
+return {
+ "danymat/neogen",
+ dependencies = "nvim-treesitter/nvim-treesitter",
+ config = true,
+ version = "*",
+ keys = {
+ vim.api.nvim_set_keymap(
+ "n",
+ "<Leader>nf",
+ ":lua require('neogen').generate({type = 'file'})<CR>",
+ { desc = "generate file", noremap = true, silent = true }
+ ),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<Leader>nn",
+ ":lua require('neogen').generate({type = 'func'})<CR>",
+ { desc = "generate function", noremap = true, silent = true }
+ ),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<Leader>nc",
+ ":lua require('neogen').generate({ type = 'class' })<CR>",
+ { desc = "generate class", noremap = true, silent = true }
+ ),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<Leader>nt",
+ ":lua require('neogen').generate({ type = 'type' })<CR>",
+ { desc = "generate type", noremap = true, silent = true }
+ ),
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/notify.lua b/ar/.config/LazyVim/lua/plugins/notify.lua
new file mode 100644
index 0000000..828aaf1
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/notify.lua
@@ -0,0 +1,6 @@
+return {
+ "rcarriga/nvim-notify",
+ opts = {
+ background_colour = "#000000",
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/nvim-neo-tree.lua b/ar/.config/LazyVim/lua/plugins/nvim-neo-tree.lua
new file mode 100644
index 0000000..ebb7387
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/nvim-neo-tree.lua
@@ -0,0 +1,16 @@
+local Util = require("lazyvim.util")
+return {
+ "nvim-neo-tree/neo-tree.nvim",
+ branch = "v3.x",
+ cmd = "Neotree",
+ opts = {},
+ keys = {
+ {
+ "<C-n>",
+ function()
+ require("neo-tree.command").execute({ toggle = false })
+ end,
+ desc = "Focus Explorer NeoTree (root dir)",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/obsidian.lua b/ar/.config/LazyVim/lua/plugins/obsidian.lua
new file mode 100644
index 0000000..ed09f64
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/obsidian.lua
@@ -0,0 +1,319 @@
+return {
+ "epwalsh/obsidian.nvim",
+ version = "*", -- recommended, use latest release instead of latest commit
+ lazy = true,
+ ft = "markdown",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "hrsh7th/nvim-cmp",
+ "nvim-telescope/telescope.nvim",
+ "epwalsh/pomo.nvim",
+ },
+ config = function()
+ require("obsidian").setup({
+ workspaces = {
+ {
+ name = "SI",
+ path = "~/Obsidian/SI",
+ },
+ },
+ notes_subdir = "/Resource/Unsorted notes",
+ log_level = vim.log.levels.INFO,
+ daily_notes = {
+ folder = "Area/Journal/Daily",
+ date_format = "%Y-%m-%d",
+ alias_format = "%B %e, %Y",
+ template = "nvim-todo-daily-template.md",
+ },
+ templates = {
+ subdir = "Resource/Templates/Notes",
+ date_format = "%Y-%m-%d",
+ time_format = "%H:%M",
+ substitutions = {
+ date_alias = function()
+ return os.date("%B %-e, %Y")
+ end,
+ folder_name = function()
+ local currentFilePath = vim.fn.expand("%:p:h")
+ local _, _, currentFolderName = currentFilePath:find("([^/]+)$")
+ currentFolderName = currentFolderName:gsub(" ", "-")
+ return currentFolderName
+ end,
+ },
+ },
+ completion = {
+ nvim_cmp = true,
+ min_chars = 2,
+ },
+ note_id_func = function(title)
+ local suffix = ""
+ if title ~= nil then
+ suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
+ else
+ for _ = 1, 4 do
+ -- suffix = suffix .. string.char(math.random(65, 90))
+ suffix = tostring(os.date("%Y-%m-%d"))
+ end
+ end
+ -- return tostring(os.date("%Y-%M-%d")) .. "-" .. suffix
+ -- return tostring(os.date("%Y-%m-%d"))
+ return suffix
+ end,
+ disable_frontmatter = true,
+ note_frontmatter_func = function(note)
+ local currentDate = os.date("%B %d, %Y")
+ -- local _, _, filename, parentPath = note.id:find("([^/]+)[^/]*$")
+ local _, _, parentPath = note.id:find(".*/(.*)/[^/]*$")
+
+ parentPath = parentPath or "unsorted_notes"
+ note:add_alias(currentDate)
+ -- note:add_tag(filename)
+ note:add_tag(parentPath)
+
+ local out = { id = os.date("%Y-%m-%d"), aliases = note.aliases, tags = note.tags }
+ if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then
+ for k, v in pairs(note.metadata) do
+ out[k] = v
+ end
+ end
+
+ return out
+ end,
+ follow_url_func = function(url)
+ vim.fn.jobstart({ "open", url }) -- Mac OS
+ -- vim.fn.jobstart({"xdg-open", url}) -- linux
+ end,
+ use_advanced_uri = false,
+ open_app_foreground = true,
+ finder = "telescope.nvim",
+ finder_mappings = {},
+ sort_by = "modified",
+ sort_reversed = true,
+ open_notes_in = "current",
+ ui = {
+ tick = 0,
+ enable = true, -- set to false to disable all additional syntax features
+ update_debounce = 200, -- update delay after a text change (in milliseconds)
+ checkboxes = {
+ [" "] = { char = "󰄱", hl_group = "ObsidianTodo" },
+ ["x"] = { char = "", hl_group = "ObsidianDone" },
+ [">"] = { char = "", hl_group = "ObsidianRightArrow" },
+ ["~"] = { char = "󰰱", hl_group = "ObsidianTilde" },
+ },
+ external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
+ reference_text = { hl_group = "ObsidianRefText" },
+ highlight_text = { hl_group = "ObsidianHighlightText" },
+ tags = { hl_group = "ObsidianTag" },
+ hl_groups = {
+ ObsidianTodo = { bold = true, fg = "#f78c6c" },
+ ObsidianDone = { bold = true, fg = "#89ddff" },
+ ObsidianRightArrow = { bold = true, fg = "#f78c6c" },
+ ObsidianTilde = { bold = true, fg = "#ff5370" },
+ ObsidianRefText = { underline = true, fg = "#c792ea" },
+ ObsidianExtLinkIcon = { fg = "#c792ea" },
+ ObsidianTag = { italic = true, fg = "#89ddff" },
+ ObsidianHighlightText = { bg = "#75662e" },
+ },
+ },
+ attachments = {
+ img_folder = "assets/imgs", -- This is the default
+ ---@param client obsidian.Client
+ ---@param path Path the absolute path to the image file
+ ---@return string
+ img_text_func = function(client, path)
+ local link_path
+ local vault_relative_path = client:vault_relative_path(path)
+ if vault_relative_path ~= nil then
+ link_path = vault_relative_path
+ else
+ link_path = tostring(path)
+ end
+ local display_name = vim.fs.basename(link_path)
+ return string.format("![%s](%s)", display_name, link_path)
+ end,
+ },
+ yaml_parser = "native",
+ mappings = {},
+ })
+ end,
+ cmd = {
+ "ObsidianOpen",
+ "ObsidianNew",
+ "ObsidianQuickSwitch",
+ "ObsidianFollowLink",
+ "ObsidianBacklinks",
+ "ObsidianToday",
+ "ObsidianYesterday",
+ "ObsidianTomorrow",
+ "ObsidianTemplate",
+ "ObsidianSearch",
+ "ObsidianWorkspace",
+ "ObsidianPasteImg",
+ "ObsidianRename",
+ "ObsidianLink",
+ "ObsidianLinkNew",
+ },
+ keys = {
+ {
+ mode = { "i", "n" },
+ "gf",
+ function()
+ return require("obsidian").util.gf_passthrough()
+ end,
+ desc = "goto file",
+ noremap = false,
+ expr = true,
+ buffer = true,
+ },
+ {
+ mode = "n",
+ "<leader>cb",
+ function()
+ return require("obsidian").util.toggle_checkbox()
+ end,
+ buffer = true,
+ desc = "Check Box",
+ },
+ {
+ mode = "n",
+ "<leader>ono",
+ function()
+ local query = vim.fn.input("Enter query: ")
+ if query and #query > 0 then
+ vim.cmd("ObsidianOpen " .. query)
+ end
+ end,
+ desc = "Open Note",
+ },
+ {
+ mode = "n",
+ "<leader>onn",
+ function()
+ local title = vim.fn.input("Enter title: ")
+ if title and #title > 0 then
+ vim.cmd("ObsidianNew " .. title)
+ end
+ end,
+ desc = "New Note",
+ },
+ {
+ mode = "n",
+ "<leader>os",
+ "<cmd> ObsidianQuickSwitch <CR>",
+ desc = "Quick Switch",
+ },
+ {
+ mode = "n",
+ "<leader>o]",
+ "<cmd> ObsidianFollowLink <CR>",
+ desc = "Follow Link",
+ },
+ {
+ mode = "n",
+ "<leader>o[",
+ "<cmd> ObsidianBacklinks <CR>",
+ desc = "Back Link",
+ },
+ {
+ mode = "n",
+ "<leader>ont",
+ function()
+ local offset = vim.fn.input("Enter offset: ")
+ if offset and #offset > 0 then
+ vim.cmd("ObsidianToday " .. offset)
+ else
+ vim.cmd("ObsidianToday")
+ end
+ end,
+ desc = "Today Note",
+ },
+ {
+ mode = "n",
+ "<leader>onh",
+ "<cmd> ObsidianYesterday <cr>",
+ desc = "Yesterday Note",
+ },
+ {
+ mode = "n",
+ "<leader>onl",
+ "<cmd> ObsidianTomorrow <cr>",
+ desc = "Tomorrow Note",
+ },
+ {
+ mode = "n",
+ "<leader>oti",
+ "<cmd>ObsidianTemplate<cr>",
+ desc = "Insert Templates",
+ },
+ {
+ mode = "n",
+ "<leader>onf",
+ function()
+ local note = vim.fn.input("Enter note: ")
+ if note and #note > 0 then
+ vim.cmd("ObsidianSearch " .. note)
+ end
+ end,
+ desc = "Search Note",
+ },
+ {
+ mode = "n",
+ "<leader>ow",
+ function()
+ local name = vim.fn.input("Enter name: ")
+ if name and #name > 0 then
+ vim.cmd("ObsidianWorkspace " .. name)
+ end
+ end,
+ desc = "Workspace Name",
+ },
+ {
+ mode = "n",
+ "<leader>opi",
+ function()
+ local image = vim.fn.input("Enter image: ")
+ if image and #image > 0 then
+ vim.cmd("ObsidianPasteImg " .. image)
+ end
+ end,
+ desc = "Paste Image",
+ },
+ {
+ mode = "n",
+ "<leader>onr",
+ function()
+ local name = vim.fn.input("Enter name: ")
+ if name and #name > 0 then
+ vim.cmd("ObsidianRename " .. name)
+ end
+ end,
+ desc = "Rename Note",
+ },
+ {
+ mode = "v",
+ "<leader>ol",
+ function()
+ local query = vim.fn.input("Enter query: ")
+ if query and #query > 0 then
+ vim.cmd("ObsidianLink " .. query)
+ else
+ vim.cmd("ObsidianLink")
+ end
+ end,
+ desc = "Link Query",
+ },
+ {
+ mode = "v",
+ "<leader>onl",
+ function()
+ local note = vim.fn.input("Enter note: ")
+ if note and #note > 0 then
+ vim.cmd("ObsidianLinkNew " .. note)
+ else
+ vim.cmd("ObsidianLinkNew")
+ end
+ end,
+ desc = "New Link Note",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/oil.lua b/ar/.config/LazyVim/lua/plugins/oil.lua
new file mode 100644
index 0000000..ffbbfe9
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/oil.lua
@@ -0,0 +1,13 @@
+return {
+ "stevearc/oil.nvim",
+ opts = {},
+ -- Optional dependencies
+ dependencies = { "nvim-tree/nvim-web-devicons" },
+ cmd = { "Oil" },
+ config = function()
+ require("oil").setup()
+ end,
+ keys = {
+ vim.keymap.set("n", "-", "<cmd>Oil<cr>", { desc = "Open parent directory" }),
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/playground.lua b/ar/.config/LazyVim/lua/plugins/playground.lua
new file mode 100644
index 0000000..e696828
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/playground.lua
@@ -0,0 +1,38 @@
+return {
+ "nvim-treesitter/playground",
+ dependencies = { "nvim-treesitter/nvim-treesitter" },
+ cmd = { "TSPlaygroundToggle" },
+ config = function()
+ require("nvim-treesitter.configs").setup({
+ playground = {
+ enable = true,
+ disable = {},
+ updatetime = 25, -- Debounced time for highlighting nodes in the playground from source code
+ persist_queries = false, -- Whether the query persists across vim sessions
+ keybindings = {
+ toggle_query_editor = "o",
+ toggle_hl_groups = "i",
+ toggle_injected_languages = "t",
+ toggle_anonymous_nodes = "a",
+ toggle_language_display = "I",
+ focus_language = "f",
+ unfocus_language = "F",
+ update = "R",
+ goto_node = "<cr>",
+ show_help = "?",
+ },
+ },
+
+ query_linter = {
+ enable = true,
+ use_virtual_text = true,
+ lint_events = { "BufWrite", "CursorHold" },
+ },
+ })
+ end,
+ keys = {
+ { mode = "n", "<leader>pg", "<cmd> TSPlaygroundToggle <cr>", desc = "Toggle Playground" },
+ { mode = "n", "<leader>pc", "<cmd> TSHighlightCapturesUnderCurso <cr>", desc = "Highlight Captures" },
+ { mode = "n", "<leader>pn", "<cmd> TSNodeUnderCursor <cr>", desc = "Node Under Cursor" },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/project.lua b/ar/.config/LazyVim/lua/plugins/project.lua
new file mode 100644
index 0000000..313c8f8
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/project.lua
@@ -0,0 +1,6 @@
+return {
+ "ahmedkhalf/project.nvim",
+ keys = {
+ { "<leader>pr", "<Cmd>ProjectRoot<CR>", desc = "Project Root" },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/refactoring.lua b/ar/.config/LazyVim/lua/plugins/refactoring.lua
new file mode 100644
index 0000000..a7bdf99
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/refactoring.lua
@@ -0,0 +1,35 @@
+return {
+ "ThePrimeagen/refactoring.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "nvim-treesitter/nvim-treesitter",
+ },
+ config = function()
+ require("refactoring").setup()
+ end,
+ keys = {
+ vim.keymap.set("x", "<leader>re", function()
+ require("refactoring").refactor("Extract Function")
+ end, { desc = "Extract Function" }),
+ vim.keymap.set({ "x", "n" }, "<leader>rf", function()
+ require("refactoring").refactor("Extract Function To File")
+ end, { desc = "Extract Function To File" }),
+ -- Extract function supports only visual mode
+ vim.keymap.set("x", "<leader>rv", function()
+ require("refactoring").refactor("Extract Variable")
+ end, { desc = "Extract Variable" }),
+ -- Extract variable supports only visual mode
+ vim.keymap.set("n", "<leader>rI", function()
+ require("refactoring").refactor("Inline Function")
+ end, { desc = "Inline Function" }),
+ -- Inline func supports only normal
+ vim.keymap.set({ "n", "x" }, "<leader>ri", function()
+ require("refactoring").refactor("Inline Variable")
+ end, { desc = "Inline Variable" }),
+ -- Inline var supports both normal and visual mode
+
+ vim.keymap.set("n", "<leader>rb", function()
+ require("refactoring").refactor("Extract Block")
+ end, { desc = "Extract Block" }),
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/seoul256.lua b/ar/.config/LazyVim/lua/plugins/seoul256.lua
new file mode 100644
index 0000000..ddbc87b
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/seoul256.lua
@@ -0,0 +1,3 @@
+return {
+ "junegunn/seoul256.vim",
+}
diff --git a/ar/.config/LazyVim/lua/plugins/tagbar.lua b/ar/.config/LazyVim/lua/plugins/tagbar.lua
new file mode 100644
index 0000000..3824cfd
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/tagbar.lua
@@ -0,0 +1,12 @@
+return {
+ "preservim/tagbar",
+ cmd = "TagbarToggle",
+ keys = {
+ {
+ mode = "n",
+ "<leader>tb",
+ "TagbarToggle",
+ desc = "Toggle Tagbar",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/telescope.lua b/ar/.config/LazyVim/lua/plugins/telescope.lua
new file mode 100644
index 0000000..0a96a09
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/telescope.lua
@@ -0,0 +1,24 @@
+return {
+ "nvim-telescope/telescope.nvim",
+ keys = {
+ { "<leader>/", false },
+ { "<C-f>", false },
+ { "<C-b>", false },
+ },
+ opts = function()
+ local actions = require("telescope.actions")
+
+ return {
+ defaults = {
+ mappings = {
+ i = {
+ ["<C-j>"] = actions.preview_scrolling_down,
+ ["<C-k>"] = actions.preview_scrolling_up,
+ ["<C-h>"] = actions.preview_scrolling_left,
+ ["<C-l>"] = actions.preview_scrolling_right,
+ },
+ },
+ },
+ }
+ end,
+}
diff --git a/ar/.config/LazyVim/lua/plugins/tmux-navigator.lua b/ar/.config/LazyVim/lua/plugins/tmux-navigator.lua
new file mode 100644
index 0000000..7b37cf7
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/tmux-navigator.lua
@@ -0,0 +1,19 @@
+return {
+ {
+ "christoomey/vim-tmux-navigator",
+ cmd = {
+ "TmuxNavigateLeft",
+ "TmuxNavigateDown",
+ "TmuxNavigateUp",
+ "TmuxNavigateRight",
+ "TmuxNavigatePrevious",
+ },
+ keys = {
+ { "<c-h>", "<cmd><C-U>TmuxNavigateLeft<cr>", desc = "Tmux Navigate Left" },
+ { "<c-j>", "<cmd><C-U>TmuxNavigateDown<cr>", desc = "Tmux Navigate Down" },
+ { "<c-k>", "<cmd><C-U>TmuxNavigateUp<cr>", desc = "Tmux Navigate Up" },
+ { "<c-l>", "<cmd><C-U>TmuxNavigateRight<cr>", desc = "Tmux Navigate Right" },
+ { "<c-\\>", "<cmd><C-U>TmuxNavigatePrevious<cr>", desc = "Tmux Navigate Previous" },
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/tokyonight.lua b/ar/.config/LazyVim/lua/plugins/tokyonight.lua
new file mode 100644
index 0000000..6dcb72f
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/tokyonight.lua
@@ -0,0 +1,10 @@
+return {
+ "folke/tokyonight.nvim",
+ opts = {
+ transparent = true,
+ styles = {
+ sidebars = "transparent",
+ floats = "transparent",
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/treesj.lua b/ar/.config/LazyVim/lua/plugins/treesj.lua
new file mode 100644
index 0000000..9cfb3ff
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/treesj.lua
@@ -0,0 +1,32 @@
+return {
+ "Wansmer/treesj",
+ dependencies = { "nvim-treesitter/nvim-treesitter" },
+ config = function()
+ require("treesj").setup({
+ ---@type boolean Use default keymaps (<space>m - toggle, <space>j - join, <space>s - split)
+ use_default_keymaps = false,
+ ---@type boolean Node with syntax error will not be formatted
+ check_syntax_error = true,
+ ---If line after join will be longer than max value,
+ ---@type number If line after join will be longer than max value, node will not be formatted
+ max_join_length = 300,
+ ---Cursor behavior:
+ ---hold - cursor follows the node/place on which it was called
+ ---start - cursor jumps to the first symbol of the node being formatted
+ ---end - cursor jumps to the last symbol of the node being formatted
+ ---@type 'hold'|'start'|'end'
+ cursor_behavior = "hold",
+ ---@type boolean Notify about possible problems or not
+ notify = true,
+ ---@type boolean Use `dot` for repeat action
+ dot_repeat = true,
+ ---@type nil|function Callback for treesj error handler. func (err_text, level, ...other_text)
+ on_error = nil,
+ ---@type table Presets for languages
+ -- langs = {}, -- See the default presets in lua/treesj/langs
+ })
+ end,
+ keys = {
+ { "<leader>j", "<cmd>TSJToggle<cr>", desc = "Toggle Treesj" },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/vimwiki.lua b/ar/.config/LazyVim/lua/plugins/vimwiki.lua
new file mode 100644
index 0000000..697ef70
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/vimwiki.lua
@@ -0,0 +1,37 @@
+return {
+ "vimwiki/vimwiki",
+ cmd = { "VimwikiIndex", "Vimwiki2HTML", "VimwikiAll2HTML" },
+ keys = {
+ vim.api.nvim_set_keymap("n", "<leader>|", ":VimwikiSplitLink<CR>", { silent = true, desc = "Horizontal Split" }),
+ vim.api.nvim_set_keymap("n", "<leader>-", ":VimwikiVSplitLink<CR>", { silent = true, desc = "Vertical Split" }),
+ vim.api.nvim_set_keymap("n", "<leader>va", ":VimwikiAll2HTML<CR>", { silent = true, desc = "All Vimwiki to HTML" }),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>vc",
+ ":VimwikiColorize<CR>",
+ { silent = true, desc = "Colorize line or selection" }
+ ),
+ vim.api.nvim_set_keymap("n", "<leader>vd", ":VimwikiDeleteFile<CR>", { silent = true, desc = "Delete Wiki Page" }),
+ vim.api.nvim_set_keymap("n", "<leader>vh", ":Vimwiki2HTML<CR>", { silent = true, desc = "Vimwiki to HTML" }),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>vH",
+ ":Vimwiki2HTMLBrowse<CR>",
+ { silent = true, desc = "Convert current wiki to HTML" }
+ ),
+ vim.api.nvim_set_keymap("n", "<leader>vw", ":VimwikiIndex<CR>", { silent = true, desc = "Vimwiki Index" }),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>vn",
+ ":VimwikiGoto<CR>",
+ { silent = true, desc = "Goto or Create New Wiki Page" }
+ ),
+ vim.api.nvim_set_keymap("n", "<leader>vr", ":VimwikiRenameFile<CR>", { silent = true, desc = "Rename Wiki Page" }),
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>vu",
+ ":VimwikiDiaryGenerateLinks<CR>",
+ { silent = true, desc = "Update Diary" }
+ ),
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/which-key.lua b/ar/.config/LazyVim/lua/plugins/which-key.lua
new file mode 100644
index 0000000..94a6b3f
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/which-key.lua
@@ -0,0 +1,55 @@
+return {
+ "folke/which-key.nvim",
+ opts = {
+ plugins = { spelling = true },
+ defaults = {
+ mode = { "n", "v" },
+ ["g"] = { name = "+goto" },
+ ["gz"] = { name = "+surround" },
+ ["z"] = { name = "+fold" },
+ ["]"] = { name = "+next" },
+ ["["] = { name = "+prev" },
+ ["="] = { name = "+line paste" },
+ ["\\"] = { name = "+local leader" },
+ ["<leader>"] = { name = "+leader" },
+ ["<leader><"] = { name = "+B/A paste & L/R indent" },
+ ["<leader>="] = { name = "+paste & B/A filtter" },
+ ["<leader>>"] = { name = "+B/A paste & L/R indent" },
+ ["<leader>["] = { name = "+indent B/A cursor" },
+ ["<leader>]"] = { name = "+indent B/A cursor" },
+ ["<leader><tab>"] = { name = "+tabs" },
+ ["<leader>b"] = { name = "+buffer" },
+ ["<leader>c"] = { name = "+code" },
+ ["<leader>cp"] = { name = "+copy" },
+ ["<leader>dd"] = { name = "+db" },
+ ["<leader>dP"] = { name = "+class/method" },
+ ["<leader>f"] = { name = "+file/find" },
+ ["<leader>h"] = { name = "+harpoon" },
+ ["<leader>g"] = { name = "+git" },
+ ["<leader>gh"] = { name = "+hunks" },
+ ["<leader>gz"] = { name = "+surround" },
+ ["<leader>m"] = { name = "+mason" },
+ ["<leader>n"] = { name = "+annotation" },
+ ["<leader>o"] = { name = "+obsidian" },
+ ["<leader>on"] = { name = "+note" },
+ ["<leader>op"] = { name = "+paste" },
+ ["<leader>ot"] = { name = "+template" },
+ ["<leader>p"] = { name = "+playground" },
+ ["<leader>q"] = { name = "+quit/session" },
+ ["<leader>r"] = { name = "+refactoring" },
+ ["<leader>s"] = { name = "+search" },
+ ["<leader>u"] = { name = "+ui" },
+ ["<leader>w"] = { name = "+windows/which-key" },
+ ["<leader>x"] = { name = "+trouble/quickfix" },
+ },
+ },
+ keys = {
+ vim.keymap.set("n", "<leader>wK", function()
+ vim.cmd("WhichKey")
+ end, { desc = "Which-key All Key" }),
+ vim.keymap.set("n", "<leader>wk", function()
+ local input = vim.fn.input("WhichKey: ")
+ vim.cmd("WhichKey " .. input)
+ end, { desc = "Which-key Query Lookup" }),
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/yanky.lua b/ar/.config/LazyVim/lua/plugins/yanky.lua
new file mode 100644
index 0000000..fb8ac2d
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/yanky.lua
@@ -0,0 +1,31 @@
+return {
+ {
+ "gbprod/yanky.nvim",
+ dependencies = not jit.os:find("Windows") and { "kkharji/sqlite.lua" } or {},
+ opts = {
+ highlight = { timer = 250 },
+ ring = { storage = jit.os:find("Windows") and "shada" or "sqlite" },
+ },
+ keys = {
+ -- stylua: ignore
+ { "<leader>h", function() require("telescope").extensions.yank_history.yank_history({ }) end, desc = "Open Yank History" },
+ { "<leader>y", "<Plug>(YankyYank)", mode = { "n", "x" }, desc = "Yank text" },
+ { "<leader>p", "<Plug>(YankyPutAfter)", mode = { "n", "x" }, desc = "Put yanked text after cursor" },
+ { "<leader>P", "<Plug>(YankyPutBefore)", mode = { "n", "x" }, desc = "Put yanked text before cursor" },
+ { "<leader>gp", "<Plug>(YankyGPutAfter)", mode = { "n", "x" }, desc = "Put yanked text after selection" },
+ { "<leader>gP", "<Plug>(YankyGPutBefore)", mode = { "n", "x" }, desc = "Put yanked text before selection" },
+ { "<leader>[y", "<Plug>(YankyCycleForward)", desc = "Cycle forward through yank history" },
+ { "<leader>]y", "<Plug>(YankyCycleBackward)", desc = "Cycle backward through yank history" },
+ { "<leader>]p", "<Plug>(YankyPutIndentAfterLinewise)", desc = "Put indented after cursor (linewise)" },
+ { "<leader>[p", "<Plug>(YankyPutIndentBeforeLinewise)", desc = "Put indented before cursor (linewise)" },
+ { "<leader>]P", "<Plug>(YankyPutIndentAfterLinewise)", desc = "Put indented after cursor (linewise)" },
+ { "<leader>[P", "<Plug>(YankyPutIndentBeforeLinewise)", desc = "Put indented before cursor (linewise)" },
+ { "<leader>>p", "<Plug>(YankyPutIndentAfterShiftRight)", desc = "Put and indent right" },
+ { "<leader><p", "<Plug>(YankyPutIndentAfterShiftLeft)", desc = "Put and indent left" },
+ { "<leader>>P", "<Plug>(YankyPutIndentBeforeShiftRight)", desc = "Put before and indent right" },
+ { "<leader><P", "<Plug>(YankyPutIndentBeforeShiftLeft)", desc = "Put before and indent left" },
+ { "<leader>=p", "<Plug>(YankyPutAfterFilter)", desc = "Put after applying a filter" },
+ { "<leader>=P", "<Plug>(YankyPutBeforeFilter)", desc = "Put before applying a filter" },
+ },
+ },
+}
diff --git a/ar/.config/LazyVim/lua/plugins/zen-mode.lua b/ar/.config/LazyVim/lua/plugins/zen-mode.lua
new file mode 100644
index 0000000..823aa94
--- /dev/null
+++ b/ar/.config/LazyVim/lua/plugins/zen-mode.lua
@@ -0,0 +1,11 @@
+return {
+ "folke/zen-mode.nvim",
+ opts = {
+ -- your configuration comes here
+ -- or leave it empty to use the default settings
+ -- refer to the configuration section below
+ },
+ keys = {
+ { "<leader><cr>", "<cmd>ZenMode<cr>", desc = "Zen mode" },
+ },
+}
diff --git a/ar/.config/LazyVim/stylua.toml b/ar/.config/LazyVim/stylua.toml
new file mode 100644
index 0000000..5d6c50d
--- /dev/null
+++ b/ar/.config/LazyVim/stylua.toml
@@ -0,0 +1,3 @@
+indent_type = "Spaces"
+indent_width = 2
+column_width = 120 \ No newline at end of file
diff --git a/ar/.config/LazyVim/vscode/easymotion-config.vim b/ar/.config/LazyVim/vscode/easymotion-config.vim
new file mode 100644
index 0000000..89c1f90
--- /dev/null
+++ b/ar/.config/LazyVim/vscode/easymotion-config.vim
@@ -0,0 +1,2 @@
+let g:EasyMotion_smartcase = 1
+nmap f <Plug>(easymotion-bd-f)
diff --git a/ar/.config/LazyVim/vscode/plugins.lua b/ar/.config/LazyVim/vscode/plugins.lua
new file mode 100644
index 0000000..920d80f
--- /dev/null
+++ b/ar/.config/LazyVim/vscode/plugins.lua
@@ -0,0 +1,153 @@
+-- plugins.lua
+
+return {
+
+ -- Alpha (Dashboard)
+ {
+ "goolord/alpha-nvim",
+ lazy = true,
+ },
+
+ -- Auto Pairs
+ {
+ "windwp/nvim-autopairs",
+ },
+
+ -- Bufferline
+ {
+ "akinsho/bufferline.nvim",
+ dependencies = {
+ "nvim-tree/nvim-web-devicons",
+ },
+ },
+
+ -- Colorscheme
+ {
+ "folke/tokyonight.nvim",
+ },
+
+ -- Comments
+ {
+ "numToStr/Comment.nvim",
+ config = function()
+ require("Comment").setup()
+ end,
+ },
+
+ -- Easymotion (VScode)
+ {
+ "ChristianChiarulli/vscode-easymotion",
+ },
+
+ -- Git Integration
+ {
+ "lewis6991/gitsigns.nvim",
+ },
+
+ -- Hop (Better Navigation)
+ {
+ "phaazon/hop.nvim",
+ lazy = true,
+ },
+
+ -- Indentation Highlighting
+ {
+ "lukas-reineke/indent-blankline.nvim",
+ },
+
+ -- Rainbow Highlighting
+ {
+ "HiPhish/nvim-ts-rainbow2",
+ },
+
+ -- Lualine
+ {
+ "nvim-lualine/lualine.nvim",
+ dependencies = {
+ "nvim-tree/nvim-web-devicons",
+ },
+ },
+
+ -- Language Support
+ {
+ "VonHeikemen/lsp-zero.nvim",
+ lazy = true,
+ branch = "v1.x",
+ dependencies = {
+ -- LSP Support
+ { "neovim/nvim-lspconfig" }, -- Required
+ { "williamboman/mason.nvim" }, -- Optional
+ { "williamboman/mason-lspconfig.nvim" }, -- Optional
+
+ -- Autocompletion
+ { "hrsh7th/nvim-cmp" }, -- Required
+ { "hrsh7th/cmp-nvim-lsp" }, -- Required
+ { "hrsh7th/cmp-buffer" }, -- Optional
+ { "hrsh7th/cmp-path" }, -- Optional
+ { "saadparwaiz1/cmp_luasnip" }, -- Optional
+ { "hrsh7th/cmp-nvim-lua" }, -- Optional
+
+ -- Snippets
+ { "L3MON4D3/LuaSnip" }, -- Required
+ { "rafamadriz/friendly-snippets" }, -- Optional
+ },
+ },
+
+ -- Nvim-tree (File Explorer)
+ {
+ "nvim-tree/nvim-tree.lua",
+ lazy = true,
+ dependencies = {
+ "nvim-tree/nvim-web-devicons",
+ },
+ },
+
+ -- Nvim-Surround (Manipulating Surroundings)
+ {
+ "kylechui/nvim-surround",
+ config = function()
+ require("nvim-surround").setup {
+ -- Configuration here, or leave empty to use defaults
+ }
+ end,
+ },
+
+ -- -- Quick-Scope
+ -- {
+ -- "unblevable/quick-scope",
+ -- },
+
+ -- Telescope (Fuzzy Finder)
+ {
+ "nvim-telescope/telescope.nvim",
+ lazy = true,
+ dependencies = {
+ { "nvim-lua/plenary.nvim" },
+ },
+ },
+
+ -- Treesitter
+ {
+ "nvim-treesitter/nvim-treesitter",
+ },
+
+ -- Toggle Term
+ {
+ "akinsho/toggleterm.nvim",
+ config = true,
+ },
+
+ -- Undo-Tree
+ {
+ "jiaoshijie/undotree",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ },
+ },
+
+ -- Which-key
+ {
+ "folke/which-key.nvim",
+ lazy = true,
+ },
+}
diff --git a/ar/.config/LazyVim/vscode/remap.lua b/ar/.config/LazyVim/vscode/remap.lua
new file mode 100644
index 0000000..e0b47c4
--- /dev/null
+++ b/ar/.config/LazyVim/vscode/remap.lua
@@ -0,0 +1,32 @@
+vim.keymap.set("n", "<leader>pv", vim.cmd.Ex)
+
+vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv")
+vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv")
+
+vim.keymap.set("n", "J", "mzJ`z")
+vim.keymap.set("n", "<C-d>", "<C-d>zz")
+vim.keymap.set("n", "<C-u>", "<C-u>zz")
+vim.keymap.set("n", "n", "nzzzv")
+vim.keymap.set("n", "N", "Nzzzv")
+
+-- greatest remap ever
+vim.keymap.set("x", "<leader>p", [["_dP]])
+
+-- next greatest remap ever : asbjornHaland
+vim.keymap.set({ "n", "v" }, "<leader>y", [["+y]])
+vim.keymap.set("n", "<leader>Y", [["+Y]])
+
+vim.keymap.set({ "n", "v" }, "<leader>d", [["_d]])
+
+-- This is going to get me cancelled
+vim.keymap.set("i", "jk", "<Esc>")
+
+vim.keymap.set("n", "Q", "<nop>")
+
+vim.keymap.set("n", "<C-k>", "<cmd>cnext<CR>zz")
+vim.keymap.set("n", "<C-j>", "<cmd>cprev<CR>zz")
+vim.keymap.set("n", "<leader>k", "<cmd>lnext<CR>zz")
+vim.keymap.set("n", "<leader>j", "<cmd>lprev<CR>zz")
+
+vim.keymap.set("n", "<leader>s", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]])
+vim.keymap.set("n", "<leader>x", "<cmd>!chmod +x %<CR>", { silent = true })
diff --git a/ar/.config/LazyVim/vscode/settings.vim b/ar/.config/LazyVim/vscode/settings.vim
new file mode 100644
index 0000000..c48a73c
--- /dev/null
+++ b/ar/.config/LazyVim/vscode/settings.vim
@@ -0,0 +1,49 @@
+function! s:closeOtherEditors()
+ call VSCodeNotify('workbench.action.closeEditorsInOtherGroups')
+ call VSCodeNotify('workbench.action.closeOtherEditors')
+endfunction
+
+function! s:manageEditorSize(...)
+ let count = a:1
+ let to = a:2
+ for i in range(1, count ? count : 1)
+ call VSCodeNotify(to == 'increase' ? 'workbench.action.increaseViewSize' : 'workbench.action.decreaseViewSize')
+ endfor
+endfunction
+
+command! -bang Only if <q-bang> == '!' | call <SID>closeOtherEditors() | else | call VSCodeNotify('workbench.action.joinAllGroups') | endif
+
+nnoremap <silent> <C-a>= :<C-u>call VSCodeNotify('workbench.action.evenEditorWidths')<CR>
+xnoremap <silent> <C-a>= :<C-u>call VSCodeNotify('workbench.action.evenEditorWidths')<CR>
+nnoremap <silent> <C-x>_ :<C-u>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
+xnoremap <silent> <C-x>_ :<C-u>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
+
+" nnoremap <silent> <C-w>> :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" xnoremap <silent> <C-w>> :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" nnoremap <silent> <C-w>+ :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" xnoremap <silent> <C-w>+ :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" nnoremap <silent> <C-w>< :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+" xnoremap <silent> <C-w>< :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+" nnoremap <silent> <C-w>- :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+" xnoremap <silent> <C-w>- :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+
+" Better Navigation
+nnoremap <silent> <C-j> :call VSCodeNotify('workbench.action.navigateDown')<CR>
+xnoremap <silent> <C-j> :call VSCodeNotify('workbench.action.navigateDown')<CR>
+nnoremap <silent> <C-k> :call VSCodeNotify('workbench.action.navigateUp')<CR>
+xnoremap <silent> <C-k> :call VSCodeNotify('workbench.action.navigateUp')<CR>
+nnoremap <silent> <C-h> :call VSCodeNotify('workbench.action.navigateLeft')<CR>
+xnoremap <silent> <C-h> :call VSCodeNotify('workbench.action.navigateLeft')<CR>
+nnoremap <silent> <C-l> :call VSCodeNotify('workbench.action.navigateRight')<CR>
+xnoremap <silent> <C-l> :call VSCodeNotify('workbench.action.navigateRight')<CR>
+
+" Bind C-/ to vscode commentary since calling from vscode produces double comments due to multiple cursors
+xnoremap <silent> <C-/> :call Comment()<CR>
+nnoremap <silent> <C-/> :call Comment()<CR>
+
+nnoremap <silent> <C-w>_ :<C-u>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
+
+nnoremap <silent> <Space> :call VSCodeNotify('whichkey.show')<CR>
+xnoremap <silent> <Space> :call VSCodeNotify('whichkey.show')<CR>
+
+" nnoremap <silent> <C-n> :call VSCodeNotify('workbench.view.explorer')<CR> \ No newline at end of file
diff --git a/ar/.config/NvChad/.ignore b/ar/.config/NvChad/.ignore
new file mode 100644
index 0000000..42677fb
--- /dev/null
+++ b/ar/.config/NvChad/.ignore
@@ -0,0 +1 @@
+!/lua/custom/
diff --git a/ar/.config/NvChad/.stylua.toml b/ar/.config/NvChad/.stylua.toml
new file mode 100644
index 0000000..ecb6dca
--- /dev/null
+++ b/ar/.config/NvChad/.stylua.toml
@@ -0,0 +1,6 @@
+column_width = 120
+line_endings = "Unix"
+indent_type = "Spaces"
+indent_width = 2
+quote_style = "AutoPreferDouble"
+call_parentheses = "None"
diff --git a/ar/.config/NvChad/LICENSE b/ar/.config/NvChad/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/ar/.config/NvChad/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/ar/.config/NvChad/db_ui/connections.json b/ar/.config/NvChad/db_ui/connections.json
new file mode 100644
index 0000000..b9034fe
--- /dev/null
+++ b/ar/.config/NvChad/db_ui/connections.json
@@ -0,0 +1 @@
+[{"url": "mysql://si@localhost:3306/si", "name": "si"}]
diff --git a/ar/.config/NvChad/db_ui/si/safTEST b/ar/.config/NvChad/db_ui/si/safTEST
new file mode 100644
index 0000000..8370f6c
--- /dev/null
+++ b/ar/.config/NvChad/db_ui/si/safTEST
@@ -0,0 +1 @@
+SELECT * FROM test;
diff --git a/ar/.config/NvChad/init.lua b/ar/.config/NvChad/init.lua
new file mode 100644
index 0000000..21f0b6f
--- /dev/null
+++ b/ar/.config/NvChad/init.lua
@@ -0,0 +1,21 @@
+require "core"
+
+local custom_init_path = vim.api.nvim_get_runtime_file("lua/custom/init.lua", false)[1]
+
+if custom_init_path then
+ dofile(custom_init_path)
+end
+
+require("core.utils").load_mappings()
+
+local lazypath = vim.fn.stdpath "data" .. "/lazy/lazy.nvim"
+
+-- bootstrap lazy.nvim!
+if not vim.loop.fs_stat(lazypath) then
+ require("core.bootstrap").gen_chadrc_template()
+ require("core.bootstrap").lazy(lazypath)
+end
+
+dofile(vim.g.base46_cache .. "defaults")
+vim.opt.rtp:prepend(lazypath)
+require "plugins"
diff --git a/ar/.config/NvChad/lua/core/bootstrap.lua b/ar/.config/NvChad/lua/core/bootstrap.lua
new file mode 100644
index 0000000..b727ba8
--- /dev/null
+++ b/ar/.config/NvChad/lua/core/bootstrap.lua
@@ -0,0 +1,62 @@
+local M = {}
+local fn = vim.fn
+
+M.echo = function(str)
+ vim.cmd "redraw"
+ vim.api.nvim_echo({ { str, "Bold" } }, true, {})
+end
+
+local function shell_call(args)
+ local output = fn.system(args)
+ assert(vim.v.shell_error == 0, "External call failed with error code: " .. vim.v.shell_error .. "\n" .. output)
+end
+
+M.lazy = function(install_path)
+ ------------- base46 ---------------
+ local lazy_path = fn.stdpath "data" .. "/lazy/base46"
+
+ M.echo " Compiling base46 theme to bytecode ..."
+
+ local base46_repo = "https://github.com/NvChad/base46"
+ shell_call { "git", "clone", "--depth", "1", "-b", "v2.0", base46_repo, lazy_path }
+ vim.opt.rtp:prepend(lazy_path)
+
+ require("base46").compile()
+
+ --------- lazy.nvim ---------------
+ M.echo " Installing lazy.nvim & plugins ..."
+ local repo = "https://github.com/folke/lazy.nvim.git"
+ shell_call { "git", "clone", "--filter=blob:none", "--branch=stable", repo, install_path }
+ vim.opt.rtp:prepend(install_path)
+
+ -- install plugins
+ require "plugins"
+
+ -- mason packages & show post_bootstrap screen
+ require "nvchad.post_install"()
+end
+
+M.gen_chadrc_template = function()
+ local path = fn.stdpath "config" .. "/lua/custom"
+
+ if fn.isdirectory(path) ~= 1 then
+ local input = fn.input "Do you want to install example custom config? (y/N): "
+
+ if input:lower() == "y" then
+ M.echo "Cloning example custom config repo..."
+ shell_call { "git", "clone", "--depth", "1", "https://github.com/NvChad/example_config", path }
+ fn.delete(path .. "/.git", "rf")
+ else
+ -- use very minimal chadrc
+ fn.mkdir(path, "p")
+
+ local file = io.open(path .. "/chadrc.lua", "w")
+ if file then
+ file:write "---@type ChadrcConfig\nlocal M = {}\n\nM.ui = { theme = 'onedark' }\n\nreturn M"
+ file:close()
+ end
+ end
+ end
+end
+
+return M
diff --git a/ar/.config/NvChad/lua/core/default_config.lua b/ar/.config/NvChad/lua/core/default_config.lua
new file mode 100644
index 0000000..639916a
--- /dev/null
+++ b/ar/.config/NvChad/lua/core/default_config.lua
@@ -0,0 +1,92 @@
+local M = {}
+
+M.options = {
+ nvchad_branch = "v2.0",
+}
+
+M.ui = {
+ ------------------------------- base46 -------------------------------------
+ -- hl = highlights
+ hl_add = {},
+ hl_override = {},
+ changed_themes = {},
+ theme_toggle = { "onedark", "one_light" },
+ theme = "onedark", -- default theme
+ transparency = false,
+ lsp_semantic_tokens = false, -- needs nvim v0.9, just adds highlight groups for lsp semantic tokens
+
+ -- https://github.com/NvChad/base46/tree/v2.0/lua/base46/extended_integrations
+ extended_integrations = {}, -- these aren't compiled by default, ex: "alpha", "notify"
+
+ -- cmp themeing
+ cmp = {
+ icons = true,
+ lspkind_text = true,
+ style = "default", -- default/flat_light/flat_dark/atom/atom_colored
+ border_color = "grey_fg", -- only applicable for "default" style, use color names from base30 variables
+ selected_item_bg = "colored", -- colored / simple
+ },
+
+ telescope = { style = "borderless" }, -- borderless / bordered
+
+ ------------------------------- nvchad_ui modules -----------------------------
+ statusline = {
+ theme = "default", -- default/vscode/vscode_colored/minimal
+ -- default/round/block/arrow separators work only for default statusline theme
+ -- round and block will work for minimal theme only
+ separator_style = "default",
+ overriden_modules = nil,
+ },
+
+ -- lazyload it when there are 1+ buffers
+ tabufline = {
+ show_numbers = false,
+ enabled = true,
+ lazyload = true,
+ overriden_modules = nil,
+ },
+
+ -- nvdash (dashboard)
+ nvdash = {
+ load_on_startup = false,
+
+ header = {
+ " ▄ ▄ ",
+ " ▄ ▄▄▄ ▄ ▄▄▄ ▄ ▄ ",
+ " █ ▄ █▄█ ▄▄▄ █ █▄█ █ █ ",
+ " ▄▄ █▄█▄▄▄█ █▄█▄█▄▄█▄▄█ █ ",
+ " ▄ █▄▄█ ▄ ▄▄ ▄█ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ",
+ " █▄▄▄▄ ▄▄▄ █ ▄ ▄▄▄ ▄ ▄▄▄ ▄ ▄ █ ▄",
+ "▄ █ █▄█ █▄█ █ █ █▄█ █ █▄█ ▄▄▄ █ █",
+ "█▄█ ▄ █▄▄█▄▄█ █ ▄▄█ █ ▄ █ █▄█▄█ █",
+ " █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ █▄█▄▄▄█ ",
+ },
+
+ buttons = {
+ { " Find File", "Spc f f", "Telescope find_files" },
+ { "󰈚 Recent Files", "Spc f o", "Telescope oldfiles" },
+ { "󰈭 Find Word", "Spc f w", "Telescope live_grep" },
+ { " Bookmarks", "Spc m a", "Telescope marks" },
+ { " Themes", "Spc t h", "Telescope themes" },
+ { " Mappings", "Spc c h", "NvCheatsheet" },
+ },
+ },
+
+ cheatsheet = { theme = "grid" }, -- simple/grid
+
+ lsp = {
+ -- show function signatures i.e args as you type
+ signature = {
+ disabled = false,
+ silent = true, -- silences 'no signature help available' message from appearing
+ },
+ },
+}
+
+M.plugins = "" -- path i.e "custom.plugins", so make custom/plugins.lua file
+
+M.lazy_nvim = require "plugins.configs.lazy_nvim" -- config for lazy.nvim startup options
+
+M.mappings = require "core.mappings"
+
+return M
diff --git a/ar/.config/NvChad/lua/core/init.lua b/ar/.config/NvChad/lua/core/init.lua
new file mode 100644
index 0000000..2316f1b
--- /dev/null
+++ b/ar/.config/NvChad/lua/core/init.lua
@@ -0,0 +1,116 @@
+local opt = vim.opt
+local g = vim.g
+local config = require("core.utils").load_config()
+
+-------------------------------------- globals -----------------------------------------
+g.nvchad_theme = config.ui.theme
+g.base46_cache = vim.fn.stdpath "data" .. "/nvchad/base46/"
+g.toggle_theme_icon = "  "
+g.transparency = config.ui.transparency
+
+-------------------------------------- options ------------------------------------------
+opt.laststatus = 3 -- global statusline
+opt.showmode = false
+
+opt.clipboard = "unnamedplus"
+opt.cursorline = true
+
+-- Indenting
+opt.expandtab = true
+opt.shiftwidth = 2
+opt.smartindent = true
+opt.tabstop = 2
+opt.softtabstop = 2
+
+opt.fillchars = { eob = " " }
+opt.ignorecase = true
+opt.smartcase = true
+opt.mouse = "a"
+
+-- Numbers
+opt.number = true
+opt.numberwidth = 2
+opt.ruler = false
+
+-- disable nvim intro
+opt.shortmess:append "sI"
+
+opt.signcolumn = "yes"
+opt.splitbelow = true
+opt.splitright = true
+opt.termguicolors = true
+opt.timeoutlen = 400
+opt.undofile = true
+
+-- interval for writing swap file to disk, also used by gitsigns
+opt.updatetime = 250
+
+-- go to previous/next line with h,l,left arrow and right arrow
+-- when cursor reaches end/beginning of line
+opt.whichwrap:append "<>[]hl"
+
+g.mapleader = " "
+
+-- disable some default providers
+-- for _, provider in ipairs { "node", "perl", "python3", "ruby" } do
+for _, provider in ipairs { "node", "perl", "ruby" } do
+ vim.g["loaded_" .. provider .. "_provider"] = 0
+end
+
+-- add binaries installed by mason.nvim to path
+local is_windows = vim.loop.os_uname().sysname == "Windows_NT"
+vim.env.PATH = vim.fn.stdpath "data" .. "/mason/bin" .. (is_windows and ";" or ":") .. vim.env.PATH
+
+-------------------------------------- autocmds ------------------------------------------
+local autocmd = vim.api.nvim_create_autocmd
+
+-- dont list quickfix buffers
+autocmd("FileType", {
+ pattern = "qf",
+ callback = function()
+ vim.opt_local.buflisted = false
+ end,
+})
+
+-- reload some chadrc options on-save
+autocmd("BufWritePost", {
+ pattern = vim.tbl_map(function(path)
+ return vim.fs.normalize(vim.loop.fs_realpath(path))
+ end, vim.fn.glob(vim.fn.stdpath "config" .. "/lua/custom/**/*.lua", true, true, true)),
+ group = vim.api.nvim_create_augroup("ReloadNvChad", {}),
+
+ callback = function(opts)
+ local fp = vim.fn.fnamemodify(vim.fs.normalize(vim.api.nvim_buf_get_name(opts.buf)), ":r") --[[@as string]]
+ local app_name = vim.env.NVIM_APPNAME and vim.env.NVIM_APPNAME or "nvim"
+ local module = string.gsub(fp, "^.*/" .. app_name .. "/lua/", ""):gsub("/", ".")
+
+ require("plenary.reload").reload_module "base46"
+ require("plenary.reload").reload_module(module)
+ require("plenary.reload").reload_module "custom.chadrc"
+
+ config = require("core.utils").load_config()
+
+ vim.g.nvchad_theme = config.ui.theme
+ vim.g.transparency = config.ui.transparency
+
+ -- statusline
+ require("plenary.reload").reload_module("nvchad.statusline." .. config.ui.statusline.theme)
+ vim.opt.statusline = "%!v:lua.require('nvchad.statusline." .. config.ui.statusline.theme .. "').run()"
+
+ -- tabufline
+ if config.ui.tabufline.enabled then
+ require("plenary.reload").reload_module "nvchad.tabufline.modules"
+ vim.opt.tabline = "%!v:lua.require('nvchad.tabufline.modules').run()"
+ end
+
+ require("base46").load_all_highlights()
+ -- vim.cmd("redraw!")
+ end,
+})
+
+-------------------------------------- commands ------------------------------------------
+local new_cmd = vim.api.nvim_create_user_command
+
+new_cmd("NvChadUpdate", function()
+ require "nvchad.updater"()
+end, {})
diff --git a/ar/.config/NvChad/lua/core/mappings.lua b/ar/.config/NvChad/lua/core/mappings.lua
new file mode 100644
index 0000000..4154bde
--- /dev/null
+++ b/ar/.config/NvChad/lua/core/mappings.lua
@@ -0,0 +1,468 @@
+-- n, v, i, t = mode names
+
+local M = {}
+
+M.general = {
+ i = {
+ -- go to beginning and end
+ ["<C-b>"] = { "<ESC>^i", "Beginning Of Line" },
+ ["<C-e>"] = { "<End>", "End Of Line" },
+
+ -- navigate within insert mode
+ ["<C-h>"] = { "<Left>", "Move Left" },
+ ["<C-l>"] = { "<Right>", "Move Right" },
+ ["<C-j>"] = { "<Down>", "Move Down" },
+ ["<C-k>"] = { "<Up>", "Move Up" },
+ },
+
+ n = {
+ ["<Esc>"] = { "<cmd> noh <CR>", "Clear Highlights" },
+ -- switch between windows
+ ["<C-h>"] = { "<C-w>h", "Window Left" },
+ ["<C-l>"] = { "<C-w>l", "Window Right" },
+ ["<C-j>"] = { "<C-w>j", "Window Down" },
+ ["<C-k>"] = { "<C-w>k", "Window Up" },
+
+ -- save
+ ["<C-s>"] = { "<cmd> w <CR>", "Save File" },
+
+ -- Copy all
+ ["<C-c>"] = { "<cmd> %y+ <CR>", "Copy whole file" },
+
+ -- line numbers
+ ["<leader>n"] = { "<cmd> set nu! <CR>", "Toggle Line Number" },
+ ["<leader>rn"] = { "<cmd> set rnu! <CR>", "Toggle Relative Number" },
+
+ -- Allow moving the cursor through wrapped lines with j, k, <Up> and <Down>
+ -- http://www.reddit.com/r/vim/comments/2k4cbr/problem_with_gj_and_gk/
+ -- empty mode is same as using <cmd> :map
+ -- also don't use g[j|k] when in operator pending mode, so it doesn't alter d, y or c behaviour
+ ["j"] = { 'v:count || mode(1)[0:1] == "no" ? "j" : "gj"', "Move Down", opts = { expr = true } },
+ ["k"] = { 'v:count || mode(1)[0:1] == "no" ? "k" : "gk"', "Move Up", opts = { expr = true } },
+ ["<Up>"] = { 'v:count || mode(1)[0:1] == "no" ? "k" : "gk"', "Move Up", opts = { expr = true } },
+ ["<Down>"] = { 'v:count || mode(1)[0:1] == "no" ? "j" : "gj"', "Move Down", opts = { expr = true } },
+
+ -- new buffer
+ ["<leader>b"] = { "<cmd> enew <CR>", "New Buffer" },
+ ["<leader>ch"] = { "<cmd> NvCheatsheet <CR>", "Mapping Cheatsheet" },
+
+ ["<leader>fm"] = {
+ function()
+ vim.lsp.buf.format { async = true }
+ end,
+ "LSP Formatting",
+ },
+ },
+
+ t = {
+ ["<C-x>"] = { vim.api.nvim_replace_termcodes("<C-\\><C-N>", true, true, true), "Escape Terminal Mode" },
+ },
+
+ v = {
+ ["<Up>"] = { 'v:count || mode(1)[0:1] == "no" ? "k" : "gk"', "Move Up", opts = { expr = true } },
+ ["<Down>"] = { 'v:count || mode(1)[0:1] == "no" ? "j" : "gj"', "Move Down", opts = { expr = true } },
+ ["<"] = { "<gv", "Indent line" },
+ [">"] = { ">gv", "Indent line" },
+ },
+
+ x = {
+ ["j"] = { 'v:count || mode(1)[0:1] == "no" ? "j" : "gj"', "Move Down", opts = { expr = true } },
+ ["k"] = { 'v:count || mode(1)[0:1] == "no" ? "k" : "gk"', "Move Up", opts = { expr = true } },
+ -- Don't copy the replaced text after pasting in visual mode
+ -- https://vim.fandom.com/wiki/Replace_a_word_with_yanked_text#Alternative_mapping_for_paste
+ ["p"] = { 'p:let @+=@0<CR>:let @"=@0<CR>', "Dont Copy Replaced Text", opts = { silent = true } },
+ },
+}
+
+M.tabufline = {
+ plugin = true,
+
+ n = {
+ -- cycle through buffers
+ ["<tab>"] = {
+ function()
+ require("nvchad.tabufline").tabuflineNext()
+ end,
+ "Goto Next Buffer",
+ },
+
+ ["<S-tab>"] = {
+ function()
+ require("nvchad.tabufline").tabuflinePrev()
+ end,
+ "Goto Prev Buffer",
+ },
+
+ -- close buffer + hide terminal buffer
+ ["<leader>x"] = {
+ function()
+ require("nvchad.tabufline").close_buffer()
+ end,
+ "Close Buffer",
+ },
+ },
+}
+
+M.comment = {
+ plugin = true,
+
+ -- toggle comment in both modes
+ n = {
+ ["<leader>/"] = {
+ function()
+ require("Comment.api").toggle.linewise.current()
+ end,
+ "Toggle Comment",
+ },
+ },
+
+ v = {
+ ["<leader>/"] = {
+ "<ESC><cmd>lua require('Comment.api').toggle.linewise(vim.fn.visualmode())<CR>",
+ "Toggle Comment",
+ },
+ },
+}
+
+M.lspconfig = {
+ plugin = true,
+
+ -- See `<cmd> :help vim.lsp.*` for documentation on any of the below functions
+
+ n = {
+ ["gD"] = {
+ function()
+ vim.lsp.buf.declaration()
+ end,
+ "LSP Declaration",
+ },
+
+ ["gd"] = {
+ function()
+ vim.lsp.buf.definition()
+ end,
+ "LSP Definition",
+ },
+
+ ["K"] = {
+ function()
+ vim.lsp.buf.hover()
+ end,
+ "LSP Hover",
+ },
+
+ ["gi"] = {
+ function()
+ vim.lsp.buf.implementation()
+ end,
+ "LSP Implementation",
+ },
+
+ ["<leader>ls"] = {
+ function()
+ vim.lsp.buf.signature_help()
+ end,
+ "LSP Signature Help",
+ },
+
+ ["<leader>D"] = {
+ function()
+ vim.lsp.buf.type_definition()
+ end,
+ "LSP Definition Type",
+ },
+
+ ["<leader>ra"] = {
+ function()
+ require("nvchad.renamer").open()
+ end,
+ "LSP Rename",
+ },
+
+ ["<leader>ca"] = {
+ function()
+ vim.lsp.buf.code_action()
+ end,
+ "LSP Code Action",
+ },
+
+ ["gr"] = {
+ function()
+ vim.lsp.buf.references()
+ end,
+ "LSP References",
+ },
+
+ ["<leader>lf"] = {
+ function()
+ vim.diagnostic.open_float { border = "rounded" }
+ end,
+ "Floating Diagnostic",
+ },
+
+ ["[d"] = {
+ function()
+ vim.diagnostic.goto_prev { float = { border = "rounded" } }
+ end,
+ "Goto Prev",
+ },
+
+ ["]d"] = {
+ function()
+ vim.diagnostic.goto_next { float = { border = "rounded" } }
+ end,
+ "Goto Next",
+ },
+
+ ["<leader>q"] = {
+ function()
+ vim.diagnostic.setloclist()
+ end,
+ "Diagnostic Setloclist",
+ },
+
+ ["<leader>wa"] = {
+ function()
+ vim.lsp.buf.add_workspace_folder()
+ end,
+ "Add Workspace Folder",
+ },
+
+ ["<leader>wr"] = {
+ function()
+ vim.lsp.buf.remove_workspace_folder()
+ end,
+ "Remove Workspace Folder",
+ },
+
+ ["<leader>wl"] = {
+ function()
+ print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
+ end,
+ "List Workspace Folders",
+ },
+ },
+
+ v = {
+ ["<leader>ca"] = {
+ function()
+ vim.lsp.buf.code_action()
+ end,
+ "LSP Code Action",
+ },
+ },
+}
+
+M.nvimtree = {
+ plugin = true,
+
+ n = {
+ -- toggle
+ ["<C-n>"] = { "<cmd> NvimTreeToggle <CR>", "Toggle Nvimtree" },
+
+ -- focus
+ ["<leader>e"] = { "<cmd> NvimTreeFocus <CR>", "Focus Nvimtree" },
+ },
+}
+
+M.telescope = {
+ plugin = true,
+
+ n = {
+ -- find
+ ["<leader>ff"] = { "<cmd> Telescope find_files <CR>", "Find Files" },
+ ["<leader>fa"] = { "<cmd> Telescope find_files follow=true no_ignore=true hidden=true <CR>", "Find All" },
+ ["<leader>fw"] = { "<cmd> Telescope live_grep <CR>", "Live Grep" },
+ ["<leader>fb"] = { "<cmd> Telescope buffers <CR>", "Find Buffers" },
+ ["<leader>fh"] = { "<cmd> Telescope help_tags <CR>", "Help Page" },
+ ["<leader>fo"] = { "<cmd> Telescope oldfiles <CR>", "Find Oldfiles" },
+ ["<leader>fz"] = { "<cmd> Telescope current_buffer_fuzzy_find <CR>", "Find in Current Buffer" },
+
+ -- git
+ ["<leader>cm"] = { "<cmd> Telescope git_commits <CR>", "Git Commits" },
+ ["<leader>gt"] = { "<cmd> Telescope git_status <CR>", "Git Status" },
+
+ -- pick a hidden term
+ ["<leader>pt"] = { "<cmd> Telescope terms <CR>", "Pick Hidden Term" },
+
+ -- theme switcher
+ ["<leader>th"] = { "<cmd> Telescope themes <CR>", "Nvchad Themes" },
+
+ ["<leader>ma"] = { "<cmd> Telescope marks <CR>", "Telescope Bookmarks" },
+ },
+}
+
+M.nvterm = {
+ plugin = true,
+
+ t = {
+ -- toggle in terminal mode
+ ["<A-i>"] = {
+ function()
+ require("nvterm.terminal").toggle "float"
+ end,
+ "Toggle Floating Term",
+ },
+
+ ["<A-h>"] = {
+ function()
+ require("nvterm.terminal").toggle "horizontal"
+ end,
+ "Toggle Horizontal Term",
+ },
+
+ ["<A-v>"] = {
+ function()
+ require("nvterm.terminal").toggle "vertical"
+ end,
+ "Toggle Vertical Term",
+ },
+ },
+
+ n = {
+ -- toggle in normal mode
+ ["<A-i>"] = {
+ function()
+ require("nvterm.terminal").toggle "float"
+ end,
+ "Toggle Floating Term",
+ },
+
+ ["<A-h>"] = {
+ function()
+ require("nvterm.terminal").toggle "horizontal"
+ end,
+ "Toggle Horizontal Term",
+ },
+
+ ["<A-v>"] = {
+ function()
+ require("nvterm.terminal").toggle "vertical"
+ end,
+ "Toggle Vertical Term",
+ },
+
+ -- new
+ ["<leader>h"] = {
+ function()
+ require("nvterm.terminal").new "horizontal"
+ end,
+ "New Horizontal Term",
+ },
+
+ ["<leader>v"] = {
+ function()
+ require("nvterm.terminal").new "vertical"
+ end,
+ "New Vertical Term",
+ },
+ },
+}
+
+M.whichkey = {
+ plugin = true,
+
+ n = {
+ ["<leader>wK"] = {
+ function()
+ vim.cmd "WhichKey"
+ end,
+ "Which-key All Keymaps",
+ },
+ ["<leader>wk"] = {
+ function()
+ local input = vim.fn.input "WhichKey: "
+ vim.cmd("WhichKey " .. input)
+ end,
+ "Which-key Query Lookup",
+ },
+ },
+}
+
+M.blankline = {
+ plugin = true,
+
+ n = {
+ ["<leader>cc"] = {
+ function()
+ local ok, start = require("indent_blankline.utils").get_current_context(
+ vim.g.indent_blankline_context_patterns,
+ vim.g.indent_blankline_use_treesitter_scope
+ )
+
+ if ok then
+ vim.api.nvim_win_set_cursor(vim.api.nvim_get_current_win(), { start, 0 })
+ vim.cmd [[normal! _]]
+ end
+ end,
+
+ "Jump To Current Context",
+ },
+ },
+}
+
+M.gitsigns = {
+ plugin = true,
+
+ n = {
+ -- Navigation through hunks
+ ["]c"] = {
+ function()
+ if vim.wo.diff then
+ return "]c"
+ end
+ vim.schedule(function()
+ require("gitsigns").next_hunk()
+ end)
+ return "<Ignore>"
+ end,
+ "Jump To Next Hunk",
+ opts = { expr = true },
+ },
+
+ ["[c"] = {
+ function()
+ if vim.wo.diff then
+ return "[c"
+ end
+ vim.schedule(function()
+ require("gitsigns").prev_hunk()
+ end)
+ return "<Ignore>"
+ end,
+ "Jump To Prev Hunk",
+ opts = { expr = true },
+ },
+
+ -- Actions
+ ["<leader>rh"] = {
+ function()
+ require("gitsigns").reset_hunk()
+ end,
+ "Reset Hunk",
+ },
+
+ ["<leader>ph"] = {
+ function()
+ require("gitsigns").preview_hunk()
+ end,
+ "Preview Hunk",
+ },
+
+ ["<leader>gb"] = {
+ function()
+ package.loaded.gitsigns.blame_line()
+ end,
+ "Blame Line",
+ },
+
+ ["<leader>td"] = {
+ function()
+ require("gitsigns").toggle_deleted()
+ end,
+ "Toggle Deleted",
+ },
+ },
+}
+
+return M
diff --git a/ar/.config/NvChad/lua/core/utils.lua b/ar/.config/NvChad/lua/core/utils.lua
new file mode 100644
index 0000000..8b2a03d
--- /dev/null
+++ b/ar/.config/NvChad/lua/core/utils.lua
@@ -0,0 +1,118 @@
+local M = {}
+local merge_tb = vim.tbl_deep_extend
+
+M.load_config = function()
+ local config = require "core.default_config"
+ local chadrc_path = vim.api.nvim_get_runtime_file("lua/custom/chadrc.lua", false)[1]
+
+ if chadrc_path then
+ local chadrc = dofile(chadrc_path)
+
+ config.mappings = M.remove_disabled_keys(chadrc.mappings, config.mappings)
+ config = merge_tb("force", config, chadrc)
+ config.mappings.disabled = nil
+ end
+
+ return config
+end
+
+M.remove_disabled_keys = function(chadrc_mappings, default_mappings)
+ if not chadrc_mappings then
+ return default_mappings
+ end
+
+ -- store keys in a array with true value to compare
+ local keys_to_disable = {}
+ for _, mappings in pairs(chadrc_mappings) do
+ for mode, section_keys in pairs(mappings) do
+ if not keys_to_disable[mode] then
+ keys_to_disable[mode] = {}
+ end
+ section_keys = (type(section_keys) == "table" and section_keys) or {}
+ for k, _ in pairs(section_keys) do
+ keys_to_disable[mode][k] = true
+ end
+ end
+ end
+
+ -- make a copy as we need to modify default_mappings
+ for section_name, section_mappings in pairs(default_mappings) do
+ for mode, mode_mappings in pairs(section_mappings) do
+ mode_mappings = (type(mode_mappings) == "table" and mode_mappings) or {}
+ for k, _ in pairs(mode_mappings) do
+ -- if key if found then remove from default_mappings
+ if keys_to_disable[mode] and keys_to_disable[mode][k] then
+ default_mappings[section_name][mode][k] = nil
+ end
+ end
+ end
+ end
+
+ return default_mappings
+end
+
+M.load_mappings = function(section, mapping_opt)
+ vim.schedule(function()
+ local function set_section_map(section_values)
+ if section_values.plugin then
+ return
+ end
+
+ section_values.plugin = nil
+
+ for mode, mode_values in pairs(section_values) do
+ local default_opts = merge_tb("force", { mode = mode }, mapping_opt or {})
+ for keybind, mapping_info in pairs(mode_values) do
+ -- merge default + user opts
+ local opts = merge_tb("force", default_opts, mapping_info.opts or {})
+
+ mapping_info.opts, opts.mode = nil, nil
+ opts.desc = mapping_info[2]
+
+ vim.keymap.set(mode, keybind, mapping_info[1], opts)
+ end
+ end
+ end
+
+ local mappings = require("core.utils").load_config().mappings
+
+ if type(section) == "string" then
+ mappings[section]["plugin"] = nil
+ mappings = { mappings[section] }
+ end
+
+ for _, sect in pairs(mappings) do
+ set_section_map(sect)
+ end
+ end)
+end
+
+M.lazy_load = function(plugin)
+ vim.api.nvim_create_autocmd({ "BufRead", "BufWinEnter", "BufNewFile" }, {
+ group = vim.api.nvim_create_augroup("BeLazyOnFileOpen" .. plugin, {}),
+ callback = function()
+ local file = vim.fn.expand "%"
+ local condition = file ~= "NvimTree_1" and file ~= "[lazy]" and file ~= ""
+
+ if condition then
+ vim.api.nvim_del_augroup_by_name("BeLazyOnFileOpen" .. plugin)
+
+ -- dont defer for treesitter as it will show slow highlighting
+ -- This deferring only happens only when we do "nvim filename"
+ if plugin ~= "nvim-treesitter" then
+ vim.schedule(function()
+ require("lazy").load { plugins = plugin }
+
+ if plugin == "nvim-lspconfig" then
+ vim.cmd "silent! do FileType"
+ end
+ end, 0)
+ else
+ require("lazy").load { plugins = plugin }
+ end
+ end
+ end,
+ })
+end
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/README.md b/ar/.config/NvChad/lua/custom/README.md
new file mode 100644
index 0000000..0cc616c
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/README.md
@@ -0,0 +1,3 @@
+# Example_config
+
+This can be used as an example custom config for NvChad. Do check the https://github.com/NvChad/nvcommunity
diff --git a/ar/.config/NvChad/lua/custom/chadrc.lua b/ar/.config/NvChad/lua/custom/chadrc.lua
new file mode 100644
index 0000000..598bc65
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/chadrc.lua
@@ -0,0 +1,54 @@
+---@type ChadrcConfig
+local M = {}
+
+-- Path to overriding theme and highlights files
+local highlights = require "custom.highlights"
+
+M.ui = {
+ -- theme
+ theme = "catppuccin",
+ theme_toggle = { "catppuccin", "everblush" },
+ transparency = true,
+
+ -- highlights
+ hl_override = highlights.override,
+ hl_add = highlights.add,
+
+ -- Nvdash
+ nvdash = {
+ load_on_startup = false,
+
+ header = {
+ " ",
+ " ██████╗ ██████╗ ██████╗ ███████ ",
+ " ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ",
+ " ██║ ██║ ██║██║ ██║█████╗ ",
+ " ██║ ██║ ██║██║ ██║██╔══╝ ",
+ " ╚██████╗╚██████╔╝██████╔╝███████╗ ",
+ " ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ",
+ },
+
+ buttons = {
+ { "󰺯 Find File", "Spc f f", "Telescope find_files" },
+ { "󱋡 Recent Files", "Spc f o", "Telescope oldfiles" },
+ { "󱩾 Find Word", "Spc f w", "Telescope live_grep" },
+ { "󰃀 Bookmarks", "Spc m a", "Telescope marks" },
+ { "󰏘 Themes", "Spc t h", "Telescope themes" },
+ { "󰌌 Mappings", "Spc c h", "NvCheatsheet" },
+ },
+ },
+
+ -- status line
+ statusline = {
+ theme = "minimal",
+ separator_style = "block",
+ overriden_modules = nil,
+ },
+}
+
+M.plugins = "custom.plugins"
+
+-- check core.mappings for table structure
+M.mappings = require "custom.mappings"
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/configs/cell_marker.lua b/ar/.config/NvChad/lua/custom/configs/cell_marker.lua
new file mode 100644
index 0000000..1d71dfb
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/configs/cell_marker.lua
@@ -0,0 +1,159 @@
+local M = {}
+
+M.get_commenter = function()
+ local commenter = { python = "# ", lua = "-- ", julia = "# ", fennel = ";; ", scala = "// ", r = "# " }
+ local bufnr = vim.api.nvim_get_current_buf()
+ local ft = vim.api.nvim_buf_get_option(bufnr, "filetype")
+ if ft == nil or ft == "" then
+ return commenter["python"]
+ elseif commenter[ft] == nil then
+ return commenter["python"]
+ end
+
+ return commenter[ft]
+end
+
+local CELL_MARKER = M.get_commenter() .. "%%"
+vim.api.nvim_set_hl(0, "CellMarkerHl", { default = true, bg = "#c5c5c5", fg = "#111111" })
+
+M.miniai_spec = function(mode)
+ local start_line = vim.fn.search("^" .. CELL_MARKER, "bcnW")
+
+ if start_line == 0 then
+ start_line = 1
+ else
+ if mode == "i" then
+ start_line = start_line + 1
+ end
+ end
+
+ local end_line = vim.fn.search("^" .. CELL_MARKER, "nW") - 1
+ if end_line == -1 then
+ end_line = vim.fn.line "$"
+ end
+
+ local last_col = math.max(vim.fn.getline(end_line):len(), 1)
+
+ local from = { line = start_line, col = 1 }
+ local to = { line = end_line, col = last_col }
+
+ return { from = from, to = to }
+end
+
+M.show_cell_markers = function()
+ require("mini.hipatterns").enable(0, {
+ highlighters = {
+ marker = { pattern = "^" .. M.get_commenter() .. "%%%%", group = "CellMarkerHl" },
+ },
+ })
+end
+
+M.select_cell = function()
+ local bufnr = vim.api.nvim_get_current_buf()
+ local current_row = vim.api.nvim_win_get_cursor(0)[1]
+ local current_col = vim.api.nvim_win_get_cursor(0)[2]
+
+ local start_line = nil
+ local end_line = nil
+
+ for line = current_row, 1, -1 do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:find("^" .. CELL_MARKER) then
+ start_line = line
+ break
+ end
+ end
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+ for line = current_row + 1, line_count do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:find("^" .. CELL_MARKER) then
+ end_line = line
+ break
+ end
+ end
+
+ if not start_line then
+ start_line = 1
+ end
+ if not end_line then
+ end_line = line_count
+ end
+ return current_row, current_col, start_line, end_line
+end
+
+M.execute_cell = function()
+ local current_row, current_col, start_line, end_line = M.select_cell()
+ if start_line and end_line then
+ vim.fn.setpos("'<", { 0, start_line + 1, 0, 0 })
+ vim.fn.setpos("'>", { 0, end_line - 1, 0, 0 })
+ require("iron.core").visual_send()
+ vim.api.nvim_win_set_cursor(0, { current_row, current_col })
+ end
+end
+
+M.delete_cell = function()
+ local _, _, start_line, end_line = M.select_cell()
+ if start_line and end_line then
+ local rows_to_select = end_line - start_line - 1
+ vim.api.nvim_win_set_cursor(0, { start_line, 0 })
+ vim.cmd("normal!V " .. rows_to_select .. "j")
+ vim.cmd "normal!d"
+ vim.cmd "normal!k"
+ end
+end
+
+M.navigate_cell = function(up)
+ local is_up = up or false
+ local _, _, start_line, end_line = M.select_cell()
+ if is_up and start_line ~= 1 then
+ vim.api.nvim_win_set_cursor(0, { start_line - 1, 0 })
+ elseif end_line then
+ local bufnr = vim.api.nvim_get_current_buf()
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+ if end_line ~= line_count then
+ vim.api.nvim_win_set_cursor(0, { end_line + 1, 0 })
+ _, _, start_line, end_line = M.select_cell()
+ vim.api.nvim_win_set_cursor(0, { end_line - 1, 0 })
+ end
+ end
+end
+
+M.move_cell = function(dir)
+ local search_res
+ local result
+ if dir == "d" then
+ search_res = vim.fn.search("^" .. CELL_MARKER, "W")
+ if search_res == 0 then
+ result = "last"
+ end
+ else
+ search_res = vim.fn.search("^" .. CELL_MARKER, "bW")
+ if search_res == 0 then
+ result = "first"
+ vim.api.nvim_win_set_cursor(0, { 1, 0 })
+ end
+ end
+
+ return result
+end
+
+M.insert_cell_before = function(content)
+ content = content or CELL_MARKER
+ local cell_object = M.miniai_spec "a"
+ vim.api.nvim_buf_set_lines(0, cell_object.from.line - 1, cell_object.from.line - 1, false, { content, "" })
+ M.move_cell "u"
+end
+
+M.insert_cell_after = function(content)
+ content = content or CELL_MARKER
+ vim.print(content)
+ local cell_object = M.miniai_spec "a"
+ vim.api.nvim_buf_set_lines(0, cell_object.to.line, cell_object.to.line, false, { content, "" })
+ M.move_cell "d"
+end
+
+M.insert_markdown_cell = function()
+ M.insert_cell_after(CELL_MARKER .. " [markdown]")
+end
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/configs/dadbod.lua b/ar/.config/NvChad/lua/custom/configs/dadbod.lua
new file mode 100644
index 0000000..90c13fb
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/configs/dadbod.lua
@@ -0,0 +1,29 @@
+local M = {}
+
+local function db_completion()
+ require("cmp").setup.buffer { sources = { { name = "vim-dadbod-completion" } } }
+end
+
+function M.setup()
+ vim.g.db_ui_save_location = vim.fn.stdpath "config" .. require("plenary.path").path.sep .. "db_ui"
+
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = {
+ "sql",
+ },
+ command = [[setlocal omnifunc=vim_dadbod_completion#omni]],
+ })
+
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = {
+ "sql",
+ "mysql",
+ "plsql",
+ },
+ callback = function()
+ vim.schedule(db_completion)
+ end,
+ })
+end
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/configs/lspconfig.lua b/ar/.config/NvChad/lua/custom/configs/lspconfig.lua
new file mode 100644
index 0000000..c4fd1db
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/configs/lspconfig.lua
@@ -0,0 +1,71 @@
+local on_attach = require("plugins.configs.lspconfig").on_attach
+local capabilities = require("plugins.configs.lspconfig").capabilities
+
+local lspconfig = require "lspconfig"
+
+-- if you just want default config for the servers then put them in a table
+local servers = { "clangd", "cssls", "emmet_language_server", "html", "julials", "sqlls", "tsserver" }
+
+for _, lsp in ipairs(servers) do
+ lspconfig[lsp].setup {
+ on_attach = on_attach,
+ capabilities = capabilities,
+ }
+end
+
+--
+-- lspconfig.pyright.setup { blabla}
+local on_attach_qmd = function(client, bufnr)
+ local function buf_set_keymap(...)
+ vim.api.nvim_buf_set_keymap(bufnr, ...)
+ end
+ local function buf_set_option(...)
+ vim.api.nvim_buf_set_option(bufnr, ...)
+ end
+
+ buf_set_option("omnifunc", "v:lua.vim.lsp.omnifunc")
+ local opts = { noremap = true, silent = true }
+end
+
+local lsp_flags = {
+ allow_incremental_sync = true,
+ debounce_text_changes = 150,
+}
+
+local cmp_nvim_lsp = require "cmp_nvim_lsp"
+local util = require "lspconfig.util"
+
+lspconfig.marksman.setup {
+ on_attach = on_attach_qmd,
+ capabilities = capabilities,
+ filetypes = { "markdown", "quarto" },
+ root_dir = util.root_pattern(".git", ".marksman.toml", "_quarto.yml"),
+}
+
+-- lspconfig.r_language_server.setup {
+-- on_attach = on_attach,
+-- capabilities = capabilities,
+-- flags = lsp_flags,
+-- settings = {
+-- r = {
+-- lsp = {
+-- rich_documentation = false,
+-- },
+-- },
+-- },
+-- }
+
+lspconfig.yamlls.setup {
+ on_attach = on_attach,
+ capabilities = capabilities,
+ flags = lsp_flags,
+ settings = {
+ yaml = {
+ schemas = {
+ -- add custom schemas here
+ -- e.g.
+ ["https://raw.githubusercontent.com/hits-mbm-dev/kimmdy/main/src/kimmdy/kimmdy-yaml-schema.json"] = "kimmdy.yml",
+ },
+ },
+ },
+}
diff --git a/ar/.config/NvChad/lua/custom/configs/null-ls.lua b/ar/.config/NvChad/lua/custom/configs/null-ls.lua
new file mode 100644
index 0000000..d703736
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/configs/null-ls.lua
@@ -0,0 +1,45 @@
+local null_ls = require "null-ls"
+local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
+
+local b = null_ls.builtins
+
+local sources = {
+ -- cpp
+ b.formatting.clang_format,
+
+ -- Lua
+ b.formatting.stylua,
+
+ -- python
+ b.code_actions.refactoring,
+ b.diagnostics.mypy,
+ b.diagnostics.ruff,
+ b.diagnostics.vulture,
+ b.formatting.black,
+ -- b.formatting.pyflyby,
+ -- b.formatting.reorder_python_imports,
+
+ -- webdev stuff
+ b.formatting.deno_fmt, -- choosed deno for ts/js files cuz its very fast!
+ b.formatting.prettier.with { filetypes = { "html", "markdown", "css" } }, -- so prettier works only on these filetypes
+}
+
+null_ls.setup {
+ debug = true,
+ sources = sources,
+ on_attach = function(client, bufnr)
+ if client.supports_method "textDocument/formatting" then
+ vim.api.nvim_clear_autocmds {
+ group = augroup,
+ buffer = bufnr,
+ }
+ vim.api.nvim_create_autocmd("BufWritePre", {
+ group = augroup,
+ buffer = bufnr,
+ callback = function()
+ vim.lsp.buf.format { bufnr = bufnr }
+ end,
+ })
+ end
+ end,
+}
diff --git a/ar/.config/NvChad/lua/custom/configs/overrides.lua b/ar/.config/NvChad/lua/custom/configs/overrides.lua
new file mode 100644
index 0000000..13a0aa1
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/configs/overrides.lua
@@ -0,0 +1,247 @@
+local M = {}
+
+M.blankline = {
+ -- show_trailing_blankline_indent = true,
+ -- show_first_indent_level = true,
+ context_patterns = {
+ "block",
+ "else_clause",
+ "catch_clause",
+ "class",
+ "function",
+ "import_statement",
+ "jsx_element",
+ "jsx_self_closing_element",
+ "method",
+ "return",
+ "try_statement",
+ "^for",
+ "^if",
+ "^object",
+ "^table",
+ "^while",
+ },
+}
+
+M.treesitter = {
+ dependencies = {
+ "nvim-treesitter/nvim-treesitter-textobjects",
+ "nvim-treesitter/nvim-treesitter-context",
+ },
+
+ ensure_installed = {
+ "bash",
+ "c",
+ "css",
+ "html",
+ "javascript",
+ "julia",
+ "latex",
+ "lua",
+ "markdown",
+ "markdown_inline",
+ "python",
+ "query",
+ -- "r",
+ "scala",
+ "tsx",
+ "typescript",
+ "vim",
+ "vimdoc",
+ "yaml",
+ },
+
+ highlight = { enable = true },
+
+ indent = {
+ enable = true,
+ -- disable = {
+ -- "python"
+ -- },
+ },
+}
+
+M.mason = {
+ ensure_installed = {
+ -- c/cpp stuff
+ "clangd",
+ "clang-format",
+
+ -- julia
+ "julia-lsp",
+
+ -- lua stuff
+ "lua-language-server",
+ "stylua",
+
+ -- markdown
+ "markdownlint",
+ "markdown-toc",
+ "marksman",
+
+ -- -- python
+ "black",
+ "debugpy",
+ "mypy",
+ "ruff",
+ "pyright",
+ "vulture",
+
+ -- R
+ -- "r-languageserver",
+
+ -- -- solidity
+ -- "solidity",
+
+ -- SQL
+ "sqlls",
+ "sqlfluff",
+ "sql-formatter",
+
+ -- web dev stuff
+ "css-lsp",
+ "html-lsp",
+ "typescript-language-server",
+ "deno",
+ "prettier",
+ },
+}
+
+-- git support in nvimtree
+M.nvimtree = {
+ git = {
+ enable = true,
+ },
+
+ renderer = {
+ highlight_git = true,
+ icons = {
+ show = {
+ git = true,
+ },
+ },
+ },
+}
+
+M.cmp = {
+ branch = "main",
+ dependencies = {
+ "hrsh7th/cmp-nvim-lsp-signature-help",
+ "hrsh7th/cmp-calc",
+ "hrsh7th/cmp-emoji",
+ "f3fora/cmp-spell",
+ "ray-x/cmp-treesitter",
+ "kdheepak/cmp-latex-symbols",
+ "jmbuhr/cmp-pandoc-references",
+ "rafamadriz/friendly-snippets",
+ "onsails/lspkind-nvim",
+ },
+ config = function()
+ local cmp = require "cmp"
+ local luasnip = require "luasnip"
+ local lspkind = require "lspkind"
+ lspkind.init()
+
+ local has_words_before = function()
+ local line, col = unpack(vim.api.nvim_win_get_cursor(0))
+ return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match "%s" == nil
+ end
+
+ cmp.setup {
+ snippet = {
+ expand = function(args)
+ luasnip.lsp_expand(args.body)
+ end,
+ },
+ mapping = {
+ ["<C-f>"] = cmp.mapping.scroll_docs(-4),
+ ["<C-b>"] = cmp.mapping.scroll_docs(4),
+ ["<C-m>"] = cmp.mapping(function(fallback)
+ if luasnip.expand_or_jumpable() then
+ luasnip.expand_or_jump()
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<C-p>"] = cmp.mapping(function(fallback)
+ if luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<C-e>"] = cmp.mapping.abort(),
+ ["<CR>"] = cmp.mapping.confirm {
+ select = true,
+ },
+ ["<Tab><Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ elseif has_words_before() then
+ cmp.complete()
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<S-Tab><S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ },
+ autocomplete = false,
+ formatting = {
+ format = lspkind.cmp_format {
+ with_text = true,
+ menu = {
+ otter = "[🦦]",
+ nvim_lsp = "[LSP]",
+ luasnip = "[snip]",
+ buffer = "[buf]",
+ path = "[path]",
+ spell = "[spell]",
+ pandoc_references = "[ref]",
+ tags = "[tag]",
+ treesitter = "[TS]",
+ calc = "[calc]",
+ latex_symbols = "[tex]",
+ emoji = "[emoji]",
+ },
+ },
+ },
+ sources = {
+ { name = "otter" }, -- for code chunks in quarto
+ { name = "path" },
+ { name = "nvim_lsp" },
+ { name = "nvim_lsp_signature_help" },
+ { name = "luasnip", keyword_length = 3, max_item_count = 3 },
+ { name = "pandoc_references" },
+ { name = "buffer", keyword_length = 5, max_item_count = 3 },
+ { name = "spell" },
+ { name = "treesitter", keyword_length = 5, max_item_count = 3 },
+ { name = "calc" },
+ { name = "latex_symbols" },
+ { name = "emoji" },
+ },
+ view = {
+ entries = "native",
+ },
+ window = {
+ documentation = {
+ border = require("misc.style").border,
+ },
+ },
+ }
+
+ -- for friendly snippets
+ require("luasnip.loaders.from_vscode").lazy_load()
+ -- for custom snippets
+ require("luasnip.loaders.from_vscode").lazy_load { paths = { vim.fn.stdpath "config" .. "/snips" } }
+ -- link quarto and rmarkdown to markdown snippets
+ luasnip.filetype_extend("quarto", { "markdown" })
+ luasnip.filetype_extend("rmarkdown", { "markdown" })
+ end,
+}
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/highlights.lua b/ar/.config/NvChad/lua/custom/highlights.lua
new file mode 100644
index 0000000..efe8be1
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/highlights.lua
@@ -0,0 +1,21 @@
+-- To find any highlight groups: "<cmd> Telescope highlights"
+-- Each highlight group can take a table with variables fg, bg, bold, italic, etc
+-- base30 variable names can also be used as colors
+
+local M = {}
+
+---@type Base46HLGroupsList
+M.override = {
+ Comment = {
+ italic = true,
+ },
+}
+
+---@type HLTable
+M.add = {
+ NvimTreeOpenedFolderName = { fg = "green", bold = true },
+ HarpoonHl = { fg = "cyan", bg = "statusline_bg" },
+ HarpoonBorder = { fg = "cyan" },
+}
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/init.lua b/ar/.config/NvChad/lua/custom/init.lua
new file mode 100644
index 0000000..3dea148
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/init.lua
@@ -0,0 +1,55 @@
+-- local autocmd = vim.api.nvim_create_autocmd
+
+-- Auto resize panes when resizing nvim window
+-- autocmd("VimResized", {
+-- pattern = "*",
+-- command = "tabdo wincmd =",
+-- })
+
+-- Visual Studio Code
+if vim.g.vscode then
+ -- Plug 'asvetliakov/vim-easymotion', has('nvim') ? {} : { 'on': [] }
+ vim.cmd [[source $HOME/.config/nvim/vscode/settings.vim]]
+ vim.cmd [[source $HOME/.config/nvim/vscode/easymotion-config.vim]]
+end
+
+vim.g.python3_host_prog = "/usr/bin/python3"
+-- vim.g.loaded_python3_provider = 1
+
+local opt = vim.opt
+local api = vim.api
+
+-- Background
+-- opt.background = "dark"
+
+-- Backspace
+opt.backspace = "indent,eol,start"
+
+-- Clipboard
+opt.clipboard:append "unnamedplus"
+
+-- Column
+opt.colorcolumn = "110"
+
+-- Disable persistent undo for files in /private directory
+api.nvim_create_autocmd("BufReadPre", { pattern = "/private/*", command = "set noundofile" })
+
+-- Indenting
+opt.autoindent = true
+
+-- line numbers
+opt.number = true -- shows absolute line number on cursor line (when relative number is on)local
+opt.relativenumber = true -- show relative line numbers
+opt.scrolloff = 9
+
+-- Swap file disable
+opt.swapfile = false
+
+-- Words
+-- opt.iskeyword:append "-" -- consider string-string as whole word
+
+-- Wrap
+opt.wrap = false
+
+-- Undo persistent enable for other files
+opt.undofile = true
diff --git a/ar/.config/NvChad/lua/custom/mappings.lua b/ar/.config/NvChad/lua/custom/mappings.lua
new file mode 100644
index 0000000..6ae8646
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/mappings.lua
@@ -0,0 +1,740 @@
+---@type MappingsTable
+local M = {}
+local opts = { noremap = false, expr = true, buffer = true }
+
+M.disabled = {
+ n = {
+ ["<A-i>"] = "",
+ ["<C-a>"] = "",
+ ["<C-b>"] = "",
+ ["<C-c>"] = "",
+ ["<leader>b"] = "",
+ ["<leader>ca"] = "",
+ ["<leader>fm"] = "",
+ ["<leader>n"] = "",
+ ["<leader>rn"] = "",
+ ["<leader>td"] = "",
+ ["<tab>"] = "",
+ ["<S-tab>"] = "",
+ },
+
+ t = {
+ ["<A-i>"] = "",
+ },
+}
+
+M.general = {
+ i = {
+ -- ESC
+ ["jk"] = { "<ESC>", "Exit Insert Mode" },
+
+ -- Navigation
+ ["<C-i>"] = { "<ESC>^i", "Beginning Of Line" },
+
+ -- Lines
+ ["<C-a>"] = { "<ESC>o", "Next Line", opts = { nowait = true } },
+ ["<C-b>"] = { "<ESC>O", "Previous Line", opts = { nowait = true } },
+ ["<leader>ln"] = { "<cmd> set nu! <CR>", "Toggle Line Number" },
+ ["<leader>lrn"] = { "<cmd> set rnu! <CR>", "Toggle Relative Number" },
+ },
+
+ n = {
+ -- General
+ ["<C-c>"] = { ":", "Enter Command Mode", opts = { nowait = true } },
+ -- ["x"] = { "_x", "No Register x" },
+ ["Q"] = { "<nop>", "Nothing" },
+
+ -- Copy & Paste
+ ["<leader>cpa"] = { "<cmd> %y+ <CR>", "Copy Whole File" },
+ ["<leader>y"] = { '"+y', "yank in Vim" },
+ ["<leader>Y"] = { '"+Y', "Yank in Clipboard" },
+ -- ["<leader>s"] = { [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]], "Replace char on cursor" },
+
+ -- Formatting
+ ["<leader>lfm"] = {
+ function()
+ vim.lsp.buf.format { async = true }
+ end,
+ "LSP Formatting",
+ },
+
+ -- Line
+ -- ["<C-a>"] = { "o<ESC>", "Blank line after", opts = { nowait = true } },
+ -- ["<C-b>"] = { "O<ESC>", "Blank line before", opts = { nowait = true } },
+
+ -- Navigation
+ ["J"] = { "mzJ`z", "Join A Line To Pre-line" },
+ ["n"] = { "nzzzv", "Down Search with Mid Cursor" },
+ ["N"] = { "Nzzzv", "Up Search with Mid Cursor" },
+ ["<C-d>"] = { "<C-d>zz", "Jump 1/2 Page with Mid Cursor" },
+ ["<C-u>"] = { "<C-u>zz", "Up 1/2 Page with Mid Cursor" },
+ ["<C-f>"] = { "<C-f>zz", "Page Down with Mid Cursor" },
+ ["<C-b>"] = { "<C-b>zz", "Page Up with Mid Cursor" },
+ -- ["<C-k>"] = { "<cmd>cnext<CR>zz", "reset cursor" },
+ -- ["<C-j>"] = { "<cmd>cprev<CR>zz", "reset cursor" },
+ ["<C-h>"] = { "<cmd> TmuxNavigateLeft <CR>", "Window Left" },
+ ["<C-l>"] = { "<cmd> TmuxNavigateRight <CR>", "Window Right" },
+ ["<C-j>"] = { "<cmd> TmuxNavigateDown <CR>", "Window Down" },
+ ["<C-k>"] = { "<cmd> TmuxNavigateUp <CR>", "Window Up" },
+ ["<leader>tg"] = {
+ function()
+ require("base46").toggle_theme()
+ end,
+ "Theme Toggle",
+ },
+
+ -- New Buffer
+ ["<leader>bf"] = { "<cmd> enew <CR>", "New Buffer" },
+
+ -- Split
+ ["<leader>sv"] = { "<C-w>v", "Split Vertically" },
+ ["<leader>sh"] = { "<C-w>h", "Split Horizontally" },
+ ["<leader>se"] = { "<C-w>=", "Equal Width" },
+ ["<leader>sx"] = { "<cmd> close <CR>", "Close Split Window" },
+ },
+ v = {
+ [";"] = { ":", "Enter Command Mode", opts = { nowait = true } },
+ [">"] = { ">gv", "Indent" },
+ ["<C-n>"] = { "<ESC> ", "Exit Visual Mode" },
+ ["J"] = { ":m '>+1<CR>gv=gv", "Move A Line To Bottom" },
+ ["K"] = { ":m '<-2<CR>gv=gv", "Move A Line To Up" },
+ ["<leader>y"] = { '"+y', "yank in Vim" },
+ },
+}
+
+-- more keybinds!
+M.browse = {
+ n = {
+ ["<leader>bs"] = {
+ function()
+ require("browse").input_search()
+ end,
+ "Search Bookmarks",
+ },
+
+ ["<leader>bm"] = {
+ function()
+ require("browse").open_bookmarks()
+ end,
+ "Browse Bookmarks",
+ },
+
+ ["<leader>bb"] = {
+ function()
+ require("browse").browse()
+ end,
+ "Browse Bookmarks",
+ },
+
+ ["<leader>bd"] = {
+ function()
+ require("browse.devdocs").search()
+ end,
+ "Search Devdocs",
+ },
+
+ ["<leader>bn"] = {
+ function()
+ require("browse.mdn").search()
+ end,
+ "Search MDN",
+ },
+ },
+}
+
+M.chatgpt = {
+ n = {
+ ["<leader>cg<CR>"] = { "<cmd> ChatGPT <CR>", "ChatGPT" },
+ ["<leader>cgc"] = { "<cmd> ChatGPTCompleteCode <CR>", "ChatGPT Complete Code" },
+ ["<leader>cgt"] = { "<cmd> ChatGPTRun add_tests <CR>", "ChatGPT Add Test" },
+ ["<leader>cgf"] = { "<cmd> ChatGPTRun fix_bugs <CR>", "ChatGPT Fix Bugs" },
+ ["<leader>cga"] = { "<cmd> ChatGPTActAs <CR>", "ChatGPT Act As" },
+ ["<leader>cgi"] = { "<cmd> ChatGPTEditWithInstructions <CR>", "ChatGPT Edit" },
+ },
+
+ v = {
+ ["<leader>cge"] = { "<cmd> ChatGPTRun explain_code <CR>", "ChatGPT Explain Code" },
+ },
+}
+
+M.dadbod = {
+ n = {
+ ["<leader>db"] = { "<cmd> DBUI <CR>", "DB UI" },
+ ["<leader>du"] = { "<cmd> DBUIToggle <CR>", "Toggle UI" },
+ ["<leader>da"] = { "<cmd> DBUIAddConnection <CR>", "Add Connection" },
+ ["<leader>df"] = { "<cmd> DBUIFindBuffer <CR>", "Find buffer" },
+ ["<leader>dr"] = { "<cmd> DBUIRenameBuffer <CR>", "Rename buffer" },
+ ["<leader>dl"] = { "<cmd> DBUILastQueryInfo <CR>", "Last query info" },
+ },
+}
+
+M.gitsigns = {
+ n = {
+ ["<leader>gd"] = {
+ function()
+ require("gitsigns").toggle_deleted()
+ end,
+ "Toggle Deleted",
+ },
+ },
+}
+
+M.harpoon = {
+ n = {
+ ["<leader>a"] = {
+ function()
+ require("harpoon.mark").add_file()
+ end,
+ "Harpoon Add File",
+ },
+ ["<leader>ta"] = { "<cmd> Telescope harpoon marks <CR>", "Toggle Quick Menu" },
+ ["<leader><tab>"] = {
+ function()
+ require("harpoon.ui").toggle_quick_menu()
+ end,
+ "Harpoon Menu",
+ },
+ ["<leader>1"] = {
+ function()
+ require("harpoon.ui").nav_file(1)
+ end,
+ "Navigate to File 1",
+ },
+ ["<leader>2"] = {
+ function()
+ require("harpoon.ui").nav_file(2)
+ end,
+ "Navigate to File 2",
+ },
+ ["<leader>3"] = {
+ function()
+ require("harpoon.ui").nav_file(3)
+ end,
+ "Navigate to File 3",
+ },
+ ["<leader>4"] = {
+ function()
+ require("harpoon.ui").nav_file(4)
+ end,
+ "Navigate to File 4",
+ },
+ },
+}
+
+M.iron = {
+ n = {
+ ["<leader>ir"] = { "<cmd> IronRepl <CR>", "REPL" },
+ ["<leader>iS"] = { "<cmd> IronRestart <CR>", "Restart" },
+ ["<leader>iF"] = { "<cmd> IronFocus <CR>", "Focus" },
+ ["<leader>iH"] = { "<cmd> IronHide <CR>", "Hide" },
+ ["<leader>ism"] = {
+ function()
+ require("iron.core").run_motion "send_motion"
+ end,
+ "Send Motion",
+ },
+ ["<leader>isl"] = {
+ function()
+ require("iron.core").send_line()
+ end,
+ "Send Line",
+ },
+ ["<leader>ist"] = {
+ function()
+ require("iron.core").send_until_cursor()
+ end,
+ "Send Until Cursor",
+ },
+ ["<leader>isf"] = {
+ function()
+ require("iron.core").send_file()
+ end,
+ "Send File",
+ },
+ ["<leader>i<cr>"] = {
+ function()
+ require("iron.core").send(nil, string.char(13))
+ end,
+ "ENTER",
+ },
+ ["<leader>ii"] = {
+ function()
+ require("iron.core").send(nil, string.char(03))
+ end,
+ "Interrupt",
+ },
+ ["<leader>id"] = {
+ function()
+ require("iron.core").close_repl()
+ end,
+ "Close REPL",
+ },
+ ["<leader>ic"] = {
+ function()
+ require("iron.core").send(nil, string.char(12))
+ end,
+ "Clear",
+ },
+ ["<leader>ims"] = {
+ function()
+ require("iron.core").send_mark()
+ end,
+ "Send Mark",
+ },
+ ["<leader>imm"] = {
+ function()
+ require("iron.core").run_motion "mark_motion"
+ end,
+ "Mark Motion",
+ },
+ ["<leader>imr"] = {
+ function()
+ require("iron.marks").drop_last()
+ end,
+ "Remove Mark",
+ },
+ -- ["<leader>iar"] = {
+ -- function() end,
+ -- "+ REPL",
+ -- },
+ -- ["<leader>iam"] = {
+ -- function() end,
+ -- "+ Mark",
+ -- },
+
+ -- cell
+ ["<leader>ca"] = {
+ function()
+ require("custom.configs.cell_marker").insert_cell_after()
+ end,
+ "Insert Cell After",
+ },
+ ["<leader>cb"] = {
+ function()
+ require("custom.configs.cell_marker").insert_cell_before()
+ end,
+ "Insert Cell Before",
+ },
+ ["<leader>cm"] = {
+ function()
+ require("custom.configs.cell_marker").insert_markdown_cell()
+ end,
+ "Insert Markdown Cell",
+ },
+ ["<leader>cd"] = {
+ function()
+ require("custom.configs.cell_marker").delete_cell()
+ end,
+ "Delete Cell",
+ },
+ ["<leader>cj"] = {
+ function()
+ require("custom.configs.cell_marker").navigate_cell()
+ end,
+ "Next Cell",
+ },
+ ["<leader>ck"] = {
+ function()
+ require("custom.configs.cell_marker").navigate_cell(true)
+ end,
+ "Previous Cell",
+ },
+ ["<leader>cr"] = {
+ function()
+ require("custom.configs.cell_marker").execute_cell()
+ end,
+ "Cell Run",
+ },
+ },
+
+ v = {
+ ["<leader>ihc"] = {
+ function()
+ require("iron.marks").clear_hl()
+ end,
+ "Clear Highlight",
+ },
+ ["<leader>imv"] = {
+ function()
+ require("iron.core").mark_visual()
+ end,
+ "Mark Visual",
+ },
+ ["<leader>is"] = {
+ function()
+ require("iron.core").visual_send()
+ end,
+ "Send",
+ },
+ },
+}
+
+M.lspconfig = {
+ v = {
+ ["<leader>ca"] = {
+ function()
+ vim.lsp.buf.code_action()
+ end,
+ "LSP code action",
+ },
+ },
+}
+
+M.markdown = {
+ n = {
+ ["<leader>mp"] = { "<cmd> MarkdownPreview <CR>", "Markdown Preview" },
+ ["<leader>ms"] = { "<cmd> MarkdownPreviewStop <CR>", "Markdown Stop" },
+ ["<leader>mt"] = { "<cmd> MarkdownPreviewToggle <CR>", "Markdown Toggle" },
+ },
+}
+
+M.media = {
+ n = {
+ ["<leader>fm"] = { "<cmd> Telescope media_files <CR>", "Media Files" },
+ },
+}
+
+M.nvterm = {
+ t = {
+ ["<A-t>"] = {
+ function()
+ require("nvterm.terminal").toggle "float"
+ end,
+ "Toggle Floating Term",
+ },
+ },
+}
+
+M.playground = {
+ n = {
+ ["<leader>pg"] = { "<cmd> TSPlaygroundToggle <CR>", "Toggle Playground" },
+ ["<leader>pc"] = { "<cmd> TSHighlightCapturesUnderCurso <CR>", "Highlight Captures" },
+ ["<leader>pn"] = { "<cmd> TSNodeUnderCursor <CR>", "Node Under Cursor" },
+ },
+}
+
+M.portal = {
+ n = {
+ ["<leader>pj"] = { "<cmd> Portal jumplist backward <CR>", "Portal Jumplist" },
+ ["<leader>ph"] = {
+ function()
+ require("portal.builtin").harpoon.tunnel()
+ end,
+ "Portal Harpoon",
+ },
+ },
+}
+
+M.project = {
+ n = {
+ ["<leader>fp"] = { "<cmd> Telescope projects <CR>", "Find Project" },
+ },
+}
+
+M.refactoring = {
+ n = {
+ -- refactoring
+ ["<leader>ri"] = { "<cmd> Refactor inline_var <CR>", "Refactor Inline Var" },
+ ["<leader>rI"] = { "<cmd> Refactor inline_func <CR>", "Refactor Inline Function" },
+
+ ["<leader>rb"] = { "<cmd> Refactor extract_block <CR>", "Refactor Extract Block" },
+ ["<leader>rbf"] = { "<cmd> Refactor extract_block_to_file <CR>", "Refactor Extrack Block To File" },
+ },
+
+ x = {
+ -- copy & paste
+ -- ["<leader>p"] = { '"_dp', "preserve cut" },
+
+ -- refactoring
+ ["<leader>re"] = { "<cmd> Refactor extract <CR>", "Refactor Extract" },
+ ["<leader>rf"] = { "<cmd> Refactor extract_to_file <CR>", "Refactor Extract To File" },
+ ["<leader>rv"] = { "<cmd> Refactor extract_var <CR>", "Refactor Extract Var" },
+ ["<leader>ri"] = { "<cmd> Refactor inline_var <CR>", "Refactor Inline Var" },
+ },
+}
+
+M.sniprun = {
+ n = {
+ ["<leader>sr"] = {
+ "<cmd>lua require('sniprun').run()<CR>",
+ "Run Code",
+ },
+
+ ["<leader>ss"] = {
+ "<cmd>lua require('sniprun').reset()<CR>",
+ "Reset Code",
+ },
+
+ ["<leader>sc"] = {
+ "<cmd>lua require('sniprun').clear_repl()<CR>",
+ "Clean Repl Meomory",
+ },
+
+ ["<leader>sd"] = {
+ "<cmd>lua require('sniprun.display').close_all()<CR>",
+ "Close Display",
+ },
+ },
+
+ v = {
+ ["<C-,>"] = {
+ "<cmd>lua require('sniprun').run('v')<CR>",
+ "Run Code",
+ },
+ },
+}
+
+M.tagbar = {
+ n = {
+ ["<leader>tb"] = { "<cmd> TagbarToggle <CR>", "Toggle Tagbar" },
+ },
+}
+
+M.telescope = {
+ n = {
+ ["<leader>u"] = {
+ function()
+ require("telescope").extensions.undo.undo()
+ end,
+ "Undo Tree",
+ },
+
+ ["<leader>fc"] = { "<Cmd> Telescope frecency <CR>", "Frequent Files" },
+ },
+}
+
+M.todo = {
+ n = {
+ ["<leader>tdo"] = { "<cmd> Todo <CR>", "Todo" },
+ ["<leader>tds"] = { "<cmd> TodoTelescope <CR>", "Todo Telescope" },
+ ["<leader>tdt"] = { "<cmd> TodoTrouble <CR>", "Todo Trouble" },
+ ["<leader>tdl"] = { "<cmd> TodoLocList <CR>", "Todo Loclist" },
+ ["<leader>tdf"] = { "<cmd> TodoQuickFix <CR>", "Todo Fix" },
+ },
+}
+
+M.treesitter = {
+ n = {
+ -- ["<leader>tc"] = { "<cmd> TSContextEnable <CR>", "Toggle treesitter context" },
+ ["<leader>gc"] = {
+ function()
+ require("treesitter-context").go_to_context()
+ end,
+ "Go To Context",
+ },
+ },
+}
+
+M.trouble = {
+ n = {
+ ["<leader>trb"] = {
+ function()
+ require("trouble").toggle()
+ end,
+ "Trouble",
+ },
+ ["<leader>trw"] = {
+ function()
+ require("trouble").toggle "workspace_diagnostics"
+ end,
+ "Trouble Workspace Diagnostics",
+ },
+ ["<leader>trd"] = {
+ function()
+ require("trouble").toggle "document_diagnostics"
+ end,
+ "Trouble Document Diagnostics",
+ },
+ ["<leader>trq"] = {
+ function()
+ require("trouble").toggle "quickfix"
+ end,
+ "Trouble Quickfix",
+ },
+ ["<leader>trl"] = {
+ function()
+ require("trouble").toggle "loclist"
+ end,
+ "Trouble Loclist",
+ },
+ ["gR"] = {
+ function()
+ require("trouble").toggle "lsp_references"
+ end,
+ "Trouble Lsp References",
+ },
+ ["<leader>trc"] = {
+ function()
+ require("trouble").close()
+ end,
+ "Trouble Close",
+ },
+ ["<leader>tro"] = {
+ function()
+ require("trouble").next { skip_groups = true, jump = true }
+ end,
+ "Trouble Next",
+ },
+ ["<leader>tri"] = {
+ function()
+ require("trouble").previous { skip_groups = true, jump = true }
+ end,
+ "Trouble Previous",
+ },
+ ["<leader>trf"] = {
+ function()
+ require("trouble").first { skip_groups = true, jump = true }
+ end,
+ "Trouble First",
+ },
+ ["<leader>tre"] = {
+ function()
+ require("trouble").last { skip_groups = true, jump = true }
+ end,
+ "Trouble Last",
+ },
+ -- ["<leader>trt"] = {
+ -- function()
+ -- require("trouble.providers.telescope").open_with_trouble()
+ -- end,
+ -- "Trouble Telescope",
+ -- },
+ },
+}
+
+M.obsidian = {
+ i = {
+ ["gf"] = {
+ function()
+ require("obsidian").util.gf_passthrough()
+ end,
+ "gf",
+ opts,
+ },
+ },
+
+ n = {
+ ["gf"] = {
+ function()
+ require("obsidian").util.gf_passthrough()
+ end,
+ "gf",
+ opts,
+ },
+ ["<leader>op"] = {
+ function()
+ local query = vim.fn.input "Enter query: "
+ if query and #query > 0 then
+ vim.cmd("ObsidianOpen " .. query)
+ end
+ end,
+ "Open Notes",
+ },
+ ["<leader>on"] = {
+ function()
+ local title = vim.fn.input "Enter title: "
+ if title and #title > 0 then
+ vim.cmd("ObsidianNew " .. title)
+ end
+ end,
+ "New Notes",
+ },
+ ["<leader>ot"] = {
+ function()
+ local offset = vim.fn.input "Enter offset: "
+ if offset and #offset > 0 then
+ vim.cmd("ObsidianToday " .. offset)
+ else
+ vim.cmd "ObsidianToday"
+ end
+ end,
+ "Today Note",
+ },
+ ["<leader>of"] = {
+ function()
+ local note = vim.fn.input "Enter note: "
+ if note and #note > 0 then
+ vim.cmd("ObsidianSearch " .. note)
+ end
+ end,
+ "Search Note",
+ },
+ ["<leader>ow"] = {
+ function()
+ local name = vim.fn.input "Enter name: "
+ if name and #name > 0 then
+ vim.cmd("ObsidianWorkspace " .. name)
+ end
+ end,
+ "Workspace Name",
+ },
+ ["<leader>oi"] = {
+ function()
+ local image = vim.fn.input "Enter image: "
+ if image and #image > 0 then
+ vim.cmd("ObsidianPasteImg " .. image)
+ end
+ end,
+ "Paste Image",
+ },
+ ["<leader>or"] = {
+ function()
+ local name = vim.fn.input "Enter name: "
+ if name and #name > 0 then
+ vim.cmd("ObsidianRename " .. name)
+ end
+ end,
+ "Rename",
+ },
+ ["<leader>o["] = { "<cmd> ObsidianBacklinks <CR>", "Back Link" },
+ ["<leader>o]"] = { "<cmd> ObsidianFollowLink <CR>", "Follow Link" },
+ ["<leader>os"] = { "<cmd> ObsidianQuickSwitch <CR>", "Quick Switch" },
+ ["<leader>ol"] = { "<cmd> ObsidianTomorrow <CR>", "Tomorrow Note" },
+ ["<leader>oh"] = { "<cmd> ObsidianYesterday <CR>", "Yesterday Note" },
+ },
+
+ v = {
+ ["<leader>ol"] = {
+ function()
+ local query = vim.fn.input "Enter query: "
+ if query and #query > 0 then
+ vim.cmd("ObsidianLink " .. query)
+ else
+ vim.cmd "ObsidianLink"
+ end
+ end,
+ "Link Query",
+ },
+ ["<leader>on"] = {
+ function()
+ local note = vim.fn.input "Enter note: "
+ if note and #note > 0 then
+ vim.cmd("ObsidianLinkNew " .. note)
+ else
+ vim.cmd "ObsidianLinkNew"
+ end
+ end,
+ "New Link Note",
+ },
+ },
+}
+
+M.tabufline = {
+ n = {
+ ["L"] = {
+ function()
+ require("nvchad.tabufline").tabuflineNext()
+ end,
+ "Goto next buffer",
+ },
+
+ ["H"] = {
+ function()
+ require("nvchad.tabufline").tabuflinePrev()
+ end,
+ "Goto prev buffer",
+ },
+ },
+}
+
+return M
diff --git a/ar/.config/NvChad/lua/custom/plugins.lua b/ar/.config/NvChad/lua/custom/plugins.lua
new file mode 100644
index 0000000..38a8880
--- /dev/null
+++ b/ar/.config/NvChad/lua/custom/plugins.lua
@@ -0,0 +1,862 @@
+local overrides = require "custom.configs.overrides"
+
+---@type NvPluginSpec[]
+local plugins = {
+
+ -- Override plugin definition options
+
+ {
+ "neovim/nvim-lspconfig",
+ dependencies = {
+ -- format & linting
+ {
+ "jose-elias-alvarez/null-ls.nvim",
+ config = function()
+ require "custom.configs.null-ls"
+ end,
+ },
+ },
+ config = function()
+ require "plugins.configs.lspconfig"
+ require "custom.configs.lspconfig"
+ end, -- Override to setup mason-lspconfig
+ },
+
+ -- override plugin configs
+ {
+ "lukas-reineke/indent-blankline.nvim",
+ opts = overrides.blankline,
+ },
+ {
+ "williamboman/mason.nvim",
+ opts = overrides.mason,
+ },
+
+ {
+ "nvim-treesitter/nvim-treesitter",
+ opts = overrides.treesitter,
+ },
+
+ {
+ "nvim-tree/nvim-tree.lua",
+ opts = overrides.nvimtree,
+ },
+
+ {
+ "hrsh7th/nvim-cmp",
+ dependencies = {
+ "hrsh7th/cmp-calc",
+ "hrsh7th/cmp-cmdline",
+ "rafamadriz/friendly-snippets",
+ "honza/vim-snippets",
+ "jmbuhr/otter.nvim",
+ },
+ opts = function(_, opts)
+ -- -@param opts cmp.ConfigSchema
+ local cmp = require "cmp"
+ opts.sources = cmp.config.sources(vim.list_extend(opts.sources, { { name = "otter" } }))
+ end,
+ },
+
+ ----------------------
+ -- Install a plugin
+ ----------------------
+
+ -- better-escape
+ {
+ "max397574/better-escape.nvim",
+ event = "InsertEnter",
+ config = function()
+ require("better_escape").setup()
+ end,
+ },
+
+ -- better quick fix
+ {
+ "kevinhwang91/nvim-bqf",
+ config = function()
+ require("bqf").setup()
+ end,
+ },
+
+ -- browse
+ {
+ "lalitmee/browse.nvim",
+ dependencies = { "nvim-telescope/telescope.nvim" },
+ config = function()
+ require("browse").setup {
+ -- search provider you want to use
+ provider = "google", -- duckduckgo, bing
+
+ -- either pass it here or just pass the table to the functions
+ -- see below for more
+ bookmarks = {
+ ["github"] = {
+ ["name"] = "search github from neovim",
+ ["code_search"] = "https://github.com/search?q=%s&type=code",
+ ["repo_search"] = "https://github.com/search?q=%s&type=repositories",
+ ["issues_search"] = "https://github.com/search?q=%s&type=issues",
+ ["pulls_search"] = "https://github.com/search?q=%s&type=pullrequests",
+ },
+ },
+ }
+ end,
+ },
+
+ -- bullets
+ {
+ "dkarter/bullets.vim",
+ },
+
+ -- chafa
+ {
+ "princejoogie/chafa.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "m00qek/baleia.nvim",
+ },
+ cmd = "ViewImage",
+ config = function()
+ require("chafa").setup {
+ render = {
+ min_padding = 5,
+ show_label = true,
+ },
+ events = {
+ update_on_nvim_resize = true,
+ },
+ }
+ end,
+ },
+
+ -- chatGPT
+ {
+ "dreamsofcode-io/ChatGPT.nvim",
+ event = "VeryLazy",
+ dependencies = {
+ "MunifTanjim/nui.nvim",
+ "nvim-lua/plenary.nvim",
+ "nvim-telescope/telescope.nvim",
+ },
+ config = function()
+ require("chatgpt").setup {
+ async_api_key_cmd = "pass show api/chatGPT/nvim",
+ }
+ end,
+ },
+
+ -- cinnamon
+ {
+ "declancm/cinnamon.nvim",
+ config = function()
+ require("cinnamon").setup()
+ end,
+ },
+
+ -- diff
+ {
+ "sindrets/diffview.nvim",
+ dependencies = "nvim-lua/plenary.nvim",
+ },
+
+ -- doge
+ {
+ "kkoomen/vim-doge",
+ },
+
+ -- fzf
+ {
+ "junegunn/fzf",
+ },
+
+ -- hardhat-vscode
+ {
+ "NomicFoundation/hardhat-vscode",
+ },
+
+ -- harpoon
+ {
+ "ThePrimeagen/harpoon",
+ cmd = "Harpoon",
+ },
+
+ -- hologram
+ {
+ "edluffy/hologram.nvim",
+ config = function()
+ require("hologram").setup {
+ auto_display = true,
+ }
+ end,
+ },
+
+ -- impatient
+ {
+ "lewis6991/impatient.nvim",
+ config = function()
+ require "impatient"
+ end,
+ },
+
+ -- iron
+ {
+ "hkupty/iron.nvim",
+ event = "VeryLazy",
+ opts = function()
+ return {
+ config = {
+ scratch_repl = true,
+ repl_definition = {
+ sh = { command = { "zsh" } },
+ python = require("iron.fts.python").ipython,
+ },
+ repl_open_cmd = require("iron.view").right "40%",
+ },
+ highlight = {
+ italic = true,
+ },
+ ignore_blank_lines = true, -- ignore blank lines when sending visual select lines
+ }
+ end,
+ config = function(_, opts)
+ local iron = require "iron.core"
+ iron.setup(opts)
+ end,
+ },
+
+ -- glow
+ {
+ "ellisonleao/glow.nvim",
+ config = true,
+ cmd = "Glow",
+ },
+
+ -- -- jukit
+ -- {
+ -- "luk400/vim-jukit",
+ -- config = function()
+ -- require("jukit").setup()
+ -- -- vim.cmd "source /Users/si/.local/nvim/lazy/vim-jukit/autoload/jukit.vim"
+ -- end,
+ -- },
+
+ -- jupytext
+ {
+ "GCBallesteros/jupytext.nvim",
+ config = true,
+ lazy = false,
+ -- event = "VeryLazy",
+ },
+
+ -- jupytext
+ {
+ "goerz/jupytext.vim",
+ build = "pip3 install jupytext",
+ -- lazy = false,
+ event = "VeryLazy",
+ dependencies = { "neovim/nvim-lspconfig" },
+ opts = {},
+ config = function()
+ vim.g.jupytext_fmt = "py:percent"
+
+ -- Autocmd to set cell markers
+ vim.api.nvim_create_autocmd({ "BufEnter" }, { -- "BufWriteCmd"
+ group = vim.api.nvim_create_augroup("au_show_cell_markers", { clear = true }),
+ pattern = { "*.py", "*.r", "*.ipynb", "*.jl", "*.scala", "*.lua", "*.fnl" },
+ callback = function(event)
+ require("custom.configs.cell_marker").show_cell_markers()
+ end,
+ })
+ end,
+ },
+
+ -- latex
+ {
+ "lervag/vimtex",
+ ft = { "tex" },
+ opts = { patterns = { "*.tex" } },
+ config = function(_, opts)
+ vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = opts.patterns,
+ callback = function()
+ vim.cmd [[VimtexCompile]]
+ end,
+ })
+
+ -- Live compilation
+ vim.g.vimtex_compiler_latexmk = {
+ build_dir = ".out",
+ options = {
+ "-shell-escape",
+ "-verbose",
+ "-file-line-error",
+ "-interaction=nonstopmode",
+ "-synctex=1",
+ },
+ }
+ vim.g.vimtex_view_method = "zathura"
+ vim.g.vimtex_fold_enabled = true
+ vim.g.vimtex_syntax_conceal = {
+ accents = 1,
+ ligatures = 1,
+ cites = 1,
+ fancy = 1,
+ spacing = 0, -- default: 1
+ greek = 1,
+ math_bounds = 1,
+ math_delimiters = 1,
+ math_fracs = 1,
+ math_super_sub = 1,
+ math_symbols = 1,
+ sections = 0,
+ styles = 1,
+ }
+ end,
+ },
+
+ -- leap
+ {
+ "ggandor/leap.nvim",
+ config = function()
+ require("leap").add_default_mappings()
+ end,
+ },
+
+ -- markdown
+ {
+ "preservim/vim-markdown",
+ branch = "master",
+ dependencies = { "godlygeek/tabular" },
+ config = function()
+ vim.g.vim_markdown_folding_style_pythonic = 1
+ vim.g.vim_markdown_folding_level = 6
+ end,
+ },
+
+ -- markdown preview
+ {
+ "iamcco/markdown-preview.nvim",
+ cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
+ build = "cd app && yarn install",
+ init = function()
+ vim.g.mkdp_filetypes = { "markdown" }
+ end,
+ ft = { "markdown" },
+ },
+
+ -- marksman
+ -- {
+ -- "artempyanykh/marksman",
+ -- },
+
+ -- magma
+ -- {
+ -- "dccsillag/magma-nvim",
+ -- },
+
+ -- media
+ {
+ "nvim-telescope/telescope-media-files.nvim",
+ dependencies = {
+ "nvim-lua/popup.nvim",
+ "nvim-lua/plenary.nvim",
+ "nvim-telescope/telescope.nvim",
+ "nvim-telescope/telescope-media-files.nvim",
+ },
+ config = function()
+ require("telescope").setup {
+ extensions = {
+ media_files = {
+ filetypes = { "png", "webp", "jpg", "jpeg" },
+ find_cmd = "rg",
+ },
+ },
+ }
+ end,
+ },
+
+ -- metals
+ {
+ "scalameta/nvim-metals",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ ft = { "scala" },
+ opts = {},
+ config = function(_, opts)
+ local nvim_metals_group = vim.api.nvim_create_augroup("nvim-metals", { clear = true })
+ vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, {
+ pattern = { "*.scala", "*.sbt", "*.sc" },
+ callback = function()
+ local metals_config = require("metals").bare_config()
+ metals_config.on_attach = function(client, bufnr)
+ require("metals").setup_dap()
+ end
+ metals_config.init_options.statusBarProvider = "on"
+ metals_config.settings = {
+ showImplicitArguments = false,
+ showInferredType = true,
+ excludedPackages = {},
+ }
+ require("metals").initialize_or_attach(metals_config)
+ end,
+ group = nvim_metals_group,
+ })
+ end,
+ },
+
+ -- mini
+ { "echasnovski/mini.nvim" },
+
+ -- mini ai
+ {
+ "echasnovski/mini.ai",
+ opts = function(_, opts)
+ opts.custom_textobjects =
+ vim.tbl_extend("force", opts.custom_textobjects, { h = require("custom.configs.cell_marker").miniai_spec })
+ end,
+ },
+
+ -- neotest
+ {
+ "nvim-neotest/neotest",
+ dependencies = {
+ "stevanmilic/neotest-scala",
+ },
+ opts = function(_, opts)
+ vim.list_extend(opts.adapters, {
+ require "neotest-scala",
+ })
+ end,
+ },
+
+ -- obisidian
+ {
+ "epwalsh/obsidian.nvim",
+ version = "*", -- recommended, use latest release instead of latest commit
+ lazy = true,
+ ft = "markdown",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "hrsh7th/nvim-cmp",
+ "nvim-telescope/telescope.nvim",
+ "epwalsh/pomo.nvim",
+ },
+ config = function()
+ require("obsidian").setup {
+ workspaces = {
+ {
+ name = "SI",
+ path = "~/Obsidian/SI",
+ },
+ },
+ detect_cwd = false,
+ notes_subdir = "Area/Journal",
+ log_level = vim.log.levels.INFO,
+ daily_notes = {
+ folder = "Area/Journal/Daily",
+ date_format = "%Y-%m-%d",
+ alias_format = "%B %-d, %Y",
+ template = nil,
+ },
+ completion = {
+ nvim_cmp = true,
+ min_chars = 2,
+ new_notes_location = "current_dir",
+ prepend_note_id = true,
+ prepend_note_path = false,
+ use_path_only = false,
+ },
+ note_id_func = function(title)
+ local suffix = ""
+ if title ~= nil then
+ suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
+ else
+ for _ = 1, 4 do
+ suffix = suffix .. string.char(math.random(65, 90))
+ end
+ end
+ return tostring(os.time()) .. "-" .. suffix
+ end,
+ disable_frontmatter = false,
+ note_frontmatter_func = function(note)
+ local out = { id = note.id, aliases = note.aliases, tags = note.tags }
+ if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then
+ for k, v in pairs(note.metadata) do
+ out[k] = v
+ end
+ end
+ return out
+ end,
+ templates = {
+ subdir = "templates",
+ date_format = "%Y-%m-%d",
+ time_format = "%H:%M",
+ substitutions = {},
+ },
+ backlinks = {
+ height = 10,
+ wrap = true,
+ },
+ follow_url_func = function(url)
+ vim.fn.jobstart { "open", url } -- Mac OS
+ -- vim.fn.jobstart({"xdg-open", url}) -- linux
+ end,
+ use_advanced_uri = false,
+ open_app_foreground = false,
+ finder = "telescope.nvim",
+ sort_by = "modified",
+ sort_reversed = true,
+ open_notes_in = "current",
+ ui = {
+ enable = true, -- set to false to disable all additional syntax features
+ update_debounce = 200, -- update delay after a text change (in milliseconds)
+ checkboxes = {
+ [" "] = { char = "󰄱", hl_group = "ObsidianTodo" },
+ ["x"] = { char = "", hl_group = "ObsidianDone" },
+ [">"] = { char = "", hl_group = "ObsidianRightArrow" },
+ ["~"] = { char = "󰰱", hl_group = "ObsidianTilde" },
+ },
+ external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
+ reference_text = { hl_group = "ObsidianRefText" },
+ highlight_text = { hl_group = "ObsidianHighlightText" },
+ tags = { hl_group = "ObsidianTag" },
+ hl_groups = {
+ ObsidianTodo = { bold = true, fg = "#f78c6c" },
+ ObsidianDone = { bold = true, fg = "#89ddff" },
+ ObsidianRightArrow = { bold = true, fg = "#f78c6c" },
+ ObsidianTilde = { bold = true, fg = "#ff5370" },
+ ObsidianRefText = { underline = true, fg = "#c792ea" },
+ ObsidianExtLinkIcon = { fg = "#c792ea" },
+ ObsidianTag = { italic = true, fg = "#89ddff" },
+ ObsidianHighlightText = { bg = "#75662e" },
+ },
+ },
+ attachments = {
+ img_folder = "assets/imgs", -- This is the default
+ ---@param client obsidian.Client
+ ---@param path Path the absolute path to the image file
+ ---@return string
+ img_text_func = function(client, path)
+ local link_path
+ local vault_relative_path = client:vault_relative_path(path)
+ if vault_relative_path ~= nil then
+ link_path = vault_relative_path
+ else
+ link_path = tostring(path)
+ end
+ local display_name = vim.fs.basename(link_path)
+ return string.format("![%s](%s)", display_name, link_path)
+ end,
+ },
+ yaml_parser = "native",
+ }
+ end,
+ },
+
+ -- otter
+ {
+ "jmbuhr/otter.nvim",
+ opts = {
+ buffers = {
+ set_filetype = true,
+ },
+ },
+ },
+
+ -- playground
+ {
+ "nvim-treesitter/playground",
+ dependencies = { "nvim-treesitter/nvim-treesitter" },
+ cmd = { "TSPlaygroundToggle" },
+ config = function()
+ require("nvim-treesitter.configs").setup {
+ playground = {
+ enable = true,
+ disable = {},
+ updatetime = 25, -- Debounced time for highlighting nodes in the playground from source code
+ persist_queries = false, -- Whether the query persists across vim sessions
+ keybindings = {
+ toggle_query_editor = "o",
+ toggle_hl_groups = "i",
+ toggle_injected_languages = "t",
+ toggle_anonymous_nodes = "a",
+ toggle_language_display = "I",
+ focus_language = "f",
+ unfocus_language = "F",
+ update = "R",
+ goto_node = "<cr>",
+ show_help = "?",
+ },
+ },
+
+ query_linter = {
+ enable = true,
+ use_virtual_text = true,
+ lint_events = { "BufWrite", "CursorHold" },
+ },
+ }
+ end,
+ },
+
+ -- portal
+ -- {
+ -- "cbochs/portal.nvim",
+ -- keys = { "<leader>pj", "<leader>ph" },
+ -- },
+
+ -- project
+ {
+ "ahmedkhalf/project.nvim",
+ config = function()
+ require("project_nvim").setup()
+ require("nvim-tree").setup {
+ sync_root_with_cwd = true,
+ respect_buf_cwd = true,
+ update_focused_file = {
+ enable = true,
+ update_root = true,
+ },
+ }
+ end,
+ },
+
+ -- refactoring
+ {
+ "ThePrimeagen/refactoring.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "nvim-treesitter/nvim-treesitter",
+ },
+ config = function()
+ require("refactoring").setup()
+ end,
+ },
+
+ -- slime
+ -- {
+ -- "jpalardy/vim-slime",
+ -- init = function()
+ -- vim.b["quarto_is_" .. "python" .. "_chunk"] = false
+ -- Quarto_is_in_python_chunk = function()
+ -- require("otter.tools.functions").is_otter_language_context "python"
+ -- end
+ --
+ -- vim.cmd [[
+ -- let g:slime_dispatch_ipython_pause = 100
+ -- function SlimeOverride_EscapeText_quarto(text)
+ -- call v:lua.Quarto_is_in_python_chunk()
+ -- if exists('g:slime_python_ipython') && len(split(a:text,"\n")) > 1 && b:quarto_is_python_chunk
+ -- return ["%cpaste -q\n", g:slime_dispatch_ipython_pause, a:text, "--", "\n"]
+ -- else
+ -- return a:text
+ -- end
+ -- endfunction
+ -- ]]
+ --
+ -- local function mark_terminal()
+ -- vim.g.slime_last_channel = vim.b.terminal_job_id
+ -- vim.print(vim.g.slime_last_channel)
+ -- end
+ --
+ -- local function set_terminal()
+ -- vim.b.slime_config = { jobid = vim.g.slime_last_channel }
+ -- end
+ --
+ -- -- slime, neovvim terminal
+ -- vim.g.slime_target = "neovim"
+ -- vim.g.slime_python_ipython = 1
+ --
+ -- require("which-key").register {
+ -- ["<leader>cm"] = { mark_terminal, "mark terminal" },
+ -- ["<leader>cs"] = { set_terminal, "set terminal" },
+ -- }
+ -- end,
+ -- },
+
+ -- sniprun
+ {
+ "michaelb/sniprun",
+ -- run = "sh ./install.sh 1",
+ config = function()
+ require("sniprun").setup {
+ -- repl_enable = { "Python3_original" }, --# enable REPL-like behavior for the given interpreters
+ -- repl_disable = {}, --# disable REPL-like behavior for the given interpretersepl_enable = { "Python3_original" },
+ -- interpreter_options = { --# interpreter-specific options, see doc / :SnipInfo <name>
+ --
+ -- --# use the interpreter name as key
+ -- GFM_original = {
+ -- use_on_filetypes = { "markdown.pandoc" }, --# the 'use_on_filetypes' configuration key is
+ -- --# available for every interpreter
+ -- },
+ -- Python3_original = {
+ -- error_truncate = "auto", --# Truncate runtime errors 'long', 'short' or 'auto' # the hint is available for every interpreter # but may not be always respected
+ -- interpreter = "python3.9",
+ -- venv = { "" },
+ -- },
+ -- },
+ borders = "single",
+ }
+ end,
+ },
+
+ -- surround
+ {
+ "kylechui/nvim-surround",
+ version = "*", -- Use for stability; omit to use `main` branch for the latest features
+ event = "VeryLazy",
+ config = function()
+ require("nvim-surround").setup {
+ -- Configuration here, or leave empty to use defaults
+ }
+ end,
+ },
+
+ -- SQL
+ {
+ "tpope/vim-dadbod",
+ lazy = true,
+ dependencies = {
+ "kristijanhusak/vim-dadbod-ui",
+ { "kristijanhusak/vim-dadbod-completion", ft = { "sql", "mysql", "plsql" }, lazy = true },
+ },
+ opts = {},
+ config = function()
+ require("custom.configs.dadbod").setup()
+ end,
+ cmd = { "DBUI", "DBUIToggle", "DBUIAddConnection", "DBUIFindBuffer", "DBUIRenameBuffer", "DBUILastQueryInfo" },
+ },
+ -- {
+ -- "kristijanhusak/vim-dadbod-ui",
+ -- dependencies = {
+ -- { "tpope/vim-dadbod", lazy = true },
+ -- { "kristijanhusak/vim-dadbod-completion", ft = { "sql", "mysql", "plsql" }, lazy = true },
+ -- },
+ -- cmd = {
+ -- "DBUI",
+ -- "DBUIToggle",
+ -- "DBUIAddConnection",
+ -- "DBUIFindBuffer",
+ -- },
+ -- init = function()
+ -- -- Your DBUI configuration
+ -- vim.g.db_ui_use_nerd_fonts = 1
+ -- end,
+ -- },
+
+ -- tagbar
+ {
+ "preservim/tagbar",
+ dependencies = { "nvim-telescope/telescope.nvim" },
+ cmd = "TagbarToggle",
+ },
+
+ -- telescope-frecency
+ {
+ "nvim-telescope/telescope-frecency.nvim",
+ dependencies = {
+ "nvim-telescope/telescope.nvim",
+ },
+ config = function()
+ require("telescope").load_extension "frecency"
+ end,
+ },
+
+ -- telescope-fzf-native
+ -- {
+ -- "nvim-telescope/telescope-fzf-native.nvim",
+ -- build = "cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build",
+ -- },
+
+ -- telescope-undo
+ {
+ "debugloop/telescope-undo.nvim",
+ dependencies = { -- note how they're inverted to above example
+ {
+ "nvim-telescope/telescope.nvim",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ },
+ },
+ keys = {
+ { -- lazy style key map
+ "<leader>u",
+ "<cmd>Telescope undo<cr>",
+ desc = "undo history",
+ },
+ },
+ opts = {
+ -- don't use `defaults = { }` here, do this in the main telescope spec
+ extensions = {
+ undo = {
+ -- telescope-undo.nvim config, see below
+ },
+ -- no other extensions here, they can have their own spec too
+ },
+ },
+ config = function(_, opts)
+ -- Calling telescope's setup from multiple specs does not hurt, it will happily merge the
+ -- configs for us. We won't use data, as everything is in it's own namespace (telescope
+ -- defaults, as well as each extension).
+ require("telescope").setup(opts)
+ require("telescope").load_extension "undo"
+ end,
+ },
+
+ -- tmux
+ {
+ "christoomey/vim-tmux-navigator",
+ lazy = false,
+ },
+
+ -- todo
+ {
+ "folke/todo-comments.nvim",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ opts = {},
+ config = function()
+ require("todo-comments").setup()
+ end,
+ cmd = {
+ "Todo",
+ "TodoTrouble",
+ "TodoTelescope",
+ "TodoLocList",
+ "TodoQuickFix",
+ },
+ },
+
+ -- trouble
+ {
+ "folke/trouble.nvim",
+ dependencies = { "nvim-tree/nvim-web-devicons" },
+ opts = {},
+ config = function()
+ require("trouble").setup()
+ end,
+ },
+
+ -- visual multi
+ { "mg979/vim-visual-multi" },
+
+ -- quarto
+ -- {
+ -- "quarto-dev/quarto-nvim",
+ -- dependencies = {
+ -- "jmbuhr/otter.nvim",
+ -- "neovim/nvim-lspconfig",
+ -- },
+ -- opts = {
+ -- lspFeatures = {
+ -- languages = { "r", "python", "julia", "bash", "html", "lua" },
+ -- },
+ -- },
+ -- ft = "quarto",
+ -- cmd = {
+ -- "QuartoPreview",
+ -- "QuartoClosePreview",
+ -- "QuartoActivate",
+ -- "QuartoHelp",
+ -- "QuartoHover",
+ -- },
+ -- },
+}
+
+return plugins
diff --git a/ar/.config/NvChad/lua/plugins/configs/cmp.lua b/ar/.config/NvChad/lua/plugins/configs/cmp.lua
new file mode 100644
index 0000000..444da73
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/cmp.lua
@@ -0,0 +1,120 @@
+local cmp = require "cmp"
+
+dofile(vim.g.base46_cache .. "cmp")
+
+local cmp_ui = require("core.utils").load_config().ui.cmp
+local cmp_style = cmp_ui.style
+
+local field_arrangement = {
+ atom = { "kind", "abbr", "menu" },
+ atom_colored = { "kind", "abbr", "menu" },
+}
+
+local formatting_style = {
+ -- default fields order i.e completion word + item.kind + item.kind icons
+ fields = field_arrangement[cmp_style] or { "abbr", "kind", "menu" },
+
+ format = function(_, item)
+ local icons = require "nvchad.icons.lspkind"
+ local icon = (cmp_ui.icons and icons[item.kind]) or ""
+
+ if cmp_style == "atom" or cmp_style == "atom_colored" then
+ icon = " " .. icon .. " "
+ item.menu = cmp_ui.lspkind_text and " (" .. item.kind .. ")" or ""
+ item.kind = icon
+ else
+ icon = cmp_ui.lspkind_text and (" " .. icon .. " ") or icon
+ item.kind = string.format("%s %s", icon, cmp_ui.lspkind_text and item.kind or "")
+ end
+
+ return item
+ end,
+}
+
+local function border(hl_name)
+ return {
+ { "╭", hl_name },
+ { "─", hl_name },
+ { "╮", hl_name },
+ { "│", hl_name },
+ { "╯", hl_name },
+ { "─", hl_name },
+ { "╰", hl_name },
+ { "│", hl_name },
+ }
+end
+
+local options = {
+ completion = {
+ completeopt = "menu,menuone",
+ },
+
+ window = {
+ completion = {
+ side_padding = (cmp_style ~= "atom" and cmp_style ~= "atom_colored") and 1 or 0,
+ winhighlight = "Normal:CmpPmenu,CursorLine:CmpSel,Search:None",
+ scrollbar = false,
+ },
+ documentation = {
+ border = border "CmpDocBorder",
+ winhighlight = "Normal:CmpDoc",
+ },
+ },
+ snippet = {
+ expand = function(args)
+ require("luasnip").lsp_expand(args.body)
+ end,
+ },
+
+ formatting = formatting_style,
+
+ mapping = {
+ ["<C-p>"] = cmp.mapping.select_prev_item(),
+ ["<C-n>"] = cmp.mapping.select_next_item(),
+ ["<C-d>"] = cmp.mapping.scroll_docs(-4),
+ ["<C-f>"] = cmp.mapping.scroll_docs(4),
+ ["<C-Space>"] = cmp.mapping.complete(),
+ ["<C-e>"] = cmp.mapping.close(),
+ ["<CR>"] = cmp.mapping.confirm {
+ behavior = cmp.ConfirmBehavior.Insert,
+ select = true,
+ },
+ ["<Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ elseif require("luasnip").expand_or_jumpable() then
+ vim.fn.feedkeys(vim.api.nvim_replace_termcodes("<Plug>luasnip-expand-or-jump", true, true, true), "")
+ else
+ fallback()
+ end
+ end, {
+ "i",
+ "s",
+ }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif require("luasnip").jumpable(-1) then
+ vim.fn.feedkeys(vim.api.nvim_replace_termcodes("<Plug>luasnip-jump-prev", true, true, true), "")
+ else
+ fallback()
+ end
+ end, {
+ "i",
+ "s",
+ }),
+ },
+ sources = {
+ { name = "nvim_lsp" },
+ { name = "luasnip" },
+ { name = "buffer" },
+ { name = "nvim_lua" },
+ { name = "path" },
+ },
+}
+
+if cmp_style ~= "atom" and cmp_style ~= "atom_colored" then
+ options.window.completion.border = border "CmpBorder"
+end
+
+return options
diff --git a/ar/.config/NvChad/lua/plugins/configs/lazy_nvim.lua b/ar/.config/NvChad/lua/plugins/configs/lazy_nvim.lua
new file mode 100644
index 0000000..cd170bd
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/lazy_nvim.lua
@@ -0,0 +1,47 @@
+return {
+ defaults = { lazy = true },
+ install = { colorscheme = { "nvchad" } },
+
+ ui = {
+ icons = {
+ ft = "",
+ lazy = "󰂠 ",
+ loaded = "",
+ not_loaded = "",
+ },
+ },
+
+ performance = {
+ rtp = {
+ disabled_plugins = {
+ "2html_plugin",
+ "tohtml",
+ "getscript",
+ "getscriptPlugin",
+ "gzip",
+ "logipat",
+ "netrw",
+ "netrwPlugin",
+ "netrwSettings",
+ "netrwFileHandlers",
+ "matchit",
+ "tar",
+ "tarPlugin",
+ "rrhelper",
+ "spellfile_plugin",
+ "vimball",
+ "vimballPlugin",
+ "zip",
+ "zipPlugin",
+ "tutor",
+ "rplugin",
+ "syntax",
+ "synmenu",
+ "optwin",
+ "compiler",
+ "bugreport",
+ "ftplugin",
+ },
+ },
+ },
+}
diff --git a/ar/.config/NvChad/lua/plugins/configs/lspconfig.lua b/ar/.config/NvChad/lua/plugins/configs/lspconfig.lua
new file mode 100644
index 0000000..18e84ad
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/lspconfig.lua
@@ -0,0 +1,67 @@
+dofile(vim.g.base46_cache .. "lsp")
+require "nvchad.lsp"
+
+local M = {}
+local utils = require "core.utils"
+
+-- export on_attach & capabilities for custom lspconfigs
+
+M.on_attach = function(client, bufnr)
+ client.server_capabilities.documentFormattingProvider = false
+ client.server_capabilities.documentRangeFormattingProvider = false
+
+ utils.load_mappings("lspconfig", { buffer = bufnr })
+
+ if client.server_capabilities.signatureHelpProvider then
+ require("nvchad.signature").setup(client)
+ end
+
+ if not utils.load_config().ui.lsp_semantic_tokens and client.supports_method "textDocument/semanticTokens" then
+ client.server_capabilities.semanticTokensProvider = nil
+ end
+end
+
+M.capabilities = vim.lsp.protocol.make_client_capabilities()
+
+M.capabilities.textDocument.completion.completionItem = {
+ documentationFormat = { "markdown", "plaintext" },
+ snippetSupport = true,
+ preselectSupport = true,
+ insertReplaceSupport = true,
+ labelDetailsSupport = true,
+ deprecatedSupport = true,
+ commitCharactersSupport = true,
+ tagSupport = { valueSet = { 1 } },
+ resolveSupport = {
+ properties = {
+ "documentation",
+ "detail",
+ "additionalTextEdits",
+ },
+ },
+}
+
+require("lspconfig").lua_ls.setup {
+ on_attach = M.on_attach,
+ capabilities = M.capabilities,
+
+ settings = {
+ Lua = {
+ diagnostics = {
+ globals = { "vim" },
+ },
+ workspace = {
+ library = {
+ [vim.fn.expand "$VIMRUNTIME/lua"] = true,
+ [vim.fn.expand "$VIMRUNTIME/lua/vim/lsp"] = true,
+ [vim.fn.stdpath "data" .. "/lazy/ui/nvchad_types"] = true,
+ [vim.fn.stdpath "data" .. "/lazy/lazy.nvim/lua/lazy"] = true,
+ },
+ maxPreload = 100000,
+ preloadFileSize = 10000,
+ },
+ },
+ },
+}
+
+return M
diff --git a/ar/.config/NvChad/lua/plugins/configs/mason.lua b/ar/.config/NvChad/lua/plugins/configs/mason.lua
new file mode 100644
index 0000000..3692a15
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/mason.lua
@@ -0,0 +1,28 @@
+local options = {
+ ensure_installed = { "lua-language-server" }, -- not an option from mason.nvim
+
+ PATH = "skip",
+
+ ui = {
+ icons = {
+ package_pending = " ",
+ package_installed = "󰄳 ",
+ package_uninstalled = " 󰚌",
+ },
+
+ keymaps = {
+ toggle_server_expand = "<CR>",
+ install_server = "i",
+ update_server = "u",
+ check_server_version = "c",
+ update_all_servers = "U",
+ check_outdated_servers = "C",
+ uninstall_server = "X",
+ cancel_installation = "<C-c>",
+ },
+ },
+
+ max_concurrent_installers = 10,
+}
+
+return options
diff --git a/ar/.config/NvChad/lua/plugins/configs/nvimtree.lua b/ar/.config/NvChad/lua/plugins/configs/nvimtree.lua
new file mode 100644
index 0000000..b4a8aee
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/nvimtree.lua
@@ -0,0 +1,77 @@
+local options = {
+ filters = {
+ dotfiles = false,
+ exclude = { vim.fn.stdpath "config" .. "/lua/custom" },
+ },
+ disable_netrw = true,
+ hijack_netrw = true,
+ hijack_cursor = true,
+ hijack_unnamed_buffer_when_opening = false,
+ sync_root_with_cwd = true,
+ update_focused_file = {
+ enable = true,
+ update_root = false,
+ },
+ view = {
+ adaptive_size = false,
+ side = "left",
+ width = 30,
+ preserve_window_proportions = true,
+ },
+ git = {
+ enable = false,
+ ignore = true,
+ },
+ filesystem_watchers = {
+ enable = true,
+ },
+ actions = {
+ open_file = {
+ resize_window = true,
+ },
+ },
+ renderer = {
+ root_folder_label = false,
+ highlight_git = false,
+ highlight_opened_files = "none",
+
+ indent_markers = {
+ enable = false,
+ },
+
+ icons = {
+ show = {
+ file = true,
+ folder = true,
+ folder_arrow = true,
+ git = false,
+ },
+
+ glyphs = {
+ default = "󰈚",
+ symlink = "",
+ folder = {
+ default = "",
+ empty = "",
+ empty_open = "",
+ open = "",
+ symlink = "",
+ symlink_open = "",
+ arrow_open = "",
+ arrow_closed = "",
+ },
+ git = {
+ unstaged = "✗",
+ staged = "✓",
+ unmerged = "",
+ renamed = "➜",
+ untracked = "★",
+ deleted = "",
+ ignored = "◌",
+ },
+ },
+ },
+ },
+}
+
+return options
diff --git a/ar/.config/NvChad/lua/plugins/configs/others.lua b/ar/.config/NvChad/lua/plugins/configs/others.lua
new file mode 100644
index 0000000..dafd5a4
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/others.lua
@@ -0,0 +1,66 @@
+local M = {}
+local utils = require "core.utils"
+
+M.blankline = {
+ indentLine_enabled = 1,
+ filetype_exclude = {
+ "help",
+ "terminal",
+ "lazy",
+ "lspinfo",
+ "TelescopePrompt",
+ "TelescopeResults",
+ "mason",
+ "nvdash",
+ "nvcheatsheet",
+ "",
+ },
+ buftype_exclude = { "terminal" },
+ show_trailing_blankline_indent = false,
+ show_first_indent_level = false,
+ show_current_context = true,
+ show_current_context_start = true,
+}
+
+M.luasnip = function(opts)
+ require("luasnip").config.set_config(opts)
+
+ -- vscode format
+ require("luasnip.loaders.from_vscode").lazy_load()
+ require("luasnip.loaders.from_vscode").lazy_load { paths = vim.g.vscode_snippets_path or "" }
+
+ -- snipmate format
+ require("luasnip.loaders.from_snipmate").load()
+ require("luasnip.loaders.from_snipmate").lazy_load { paths = vim.g.snipmate_snippets_path or "" }
+
+ -- lua format
+ require("luasnip.loaders.from_lua").load()
+ require("luasnip.loaders.from_lua").lazy_load { paths = vim.g.lua_snippets_path or "" }
+
+ vim.api.nvim_create_autocmd("InsertLeave", {
+ callback = function()
+ if
+ require("luasnip").session.current_nodes[vim.api.nvim_get_current_buf()]
+ and not require("luasnip").session.jump_active
+ then
+ require("luasnip").unlink_current()
+ end
+ end,
+ })
+end
+
+M.gitsigns = {
+ signs = {
+ add = { text = "│" },
+ change = { text = "│" },
+ delete = { text = "󰍵" },
+ topdelete = { text = "‾" },
+ changedelete = { text = "~" },
+ untracked = { text = "│" },
+ },
+ on_attach = function(bufnr)
+ utils.load_mappings("gitsigns", { buffer = bufnr })
+ end,
+}
+
+return M
diff --git a/ar/.config/NvChad/lua/plugins/configs/telescope.lua b/ar/.config/NvChad/lua/plugins/configs/telescope.lua
new file mode 100644
index 0000000..91c1d3a
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/telescope.lua
@@ -0,0 +1,63 @@
+local options = {
+ defaults = {
+ vimgrep_arguments = {
+ "rg",
+ "-L",
+ "--color=never",
+ "--no-heading",
+ "--with-filename",
+ "--line-number",
+ "--column",
+ "--smart-case",
+ },
+ prompt_prefix = "  ",
+ selection_caret = " ",
+ entry_prefix = " ",
+ initial_mode = "insert",
+ selection_strategy = "reset",
+ sorting_strategy = "ascending",
+ layout_strategy = "horizontal",
+ layout_config = {
+ horizontal = {
+ prompt_position = "top",
+ preview_width = 0.55,
+ results_width = 0.8,
+ },
+ vertical = {
+ mirror = false,
+ },
+ width = 0.87,
+ height = 0.80,
+ preview_cutoff = 120,
+ },
+ file_sorter = require("telescope.sorters").get_fuzzy_file,
+ file_ignore_patterns = { "node_modules" },
+ generic_sorter = require("telescope.sorters").get_generic_fuzzy_sorter,
+ path_display = { "truncate" },
+ winblend = 0,
+ border = {},
+ borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
+ color_devicons = true,
+ set_env = { ["COLORTERM"] = "truecolor" }, -- default = nil,
+ file_previewer = require("telescope.previewers").vim_buffer_cat.new,
+ grep_previewer = require("telescope.previewers").vim_buffer_vimgrep.new,
+ qflist_previewer = require("telescope.previewers").vim_buffer_qflist.new,
+ -- Developer configurations: Not meant for general override
+ buffer_previewer_maker = require("telescope.previewers").buffer_previewer_maker,
+ mappings = {
+ n = { ["q"] = require("telescope.actions").close },
+ },
+ },
+
+ extensions_list = { "themes", "terms", "fzf" },
+ extensions = {
+ fzf = {
+ fuzzy = true,
+ override_generic_sorter = true,
+ override_file_sorter = true,
+ case_mode = "smart_case",
+ },
+ },
+}
+
+return options
diff --git a/ar/.config/NvChad/lua/plugins/configs/treesitter.lua b/ar/.config/NvChad/lua/plugins/configs/treesitter.lua
new file mode 100644
index 0000000..b21b55d
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/configs/treesitter.lua
@@ -0,0 +1,12 @@
+local options = {
+ ensure_installed = { "lua" },
+
+ highlight = {
+ enable = true,
+ use_languagetree = true,
+ },
+
+ indent = { enable = true },
+}
+
+return options
diff --git a/ar/.config/NvChad/lua/plugins/init.lua b/ar/.config/NvChad/lua/plugins/init.lua
new file mode 100644
index 0000000..a97e753
--- /dev/null
+++ b/ar/.config/NvChad/lua/plugins/init.lua
@@ -0,0 +1,273 @@
+-- All plugins have lazy=true by default,to load a plugin on startup just lazy=false
+-- List of all default plugins & their definitions
+local default_plugins = {
+
+ "nvim-lua/plenary.nvim",
+
+ {
+ "NvChad/base46",
+ branch = "v2.0",
+ build = function()
+ require("base46").load_all_highlights()
+ end,
+ },
+
+ {
+ "NvChad/ui",
+ branch = "v2.0",
+ lazy = false,
+ },
+
+ {
+ "NvChad/nvterm",
+ init = function()
+ require("core.utils").load_mappings "nvterm"
+ end,
+ config = function(_, opts)
+ require "base46.term"
+ require("nvterm").setup(opts)
+ end,
+ },
+
+ {
+ "NvChad/nvim-colorizer.lua",
+ init = function()
+ require("core.utils").lazy_load "nvim-colorizer.lua"
+ end,
+ config = function(_, opts)
+ require("colorizer").setup(opts)
+
+ -- execute colorizer as soon as possible
+ vim.defer_fn(function()
+ require("colorizer").attach_to_buffer(0)
+ end, 0)
+ end,
+ },
+
+ {
+ "nvim-tree/nvim-web-devicons",
+ opts = function()
+ return { override = require "nvchad.icons.devicons" }
+ end,
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "devicons")
+ require("nvim-web-devicons").setup(opts)
+ end,
+ },
+
+ {
+ "lukas-reineke/indent-blankline.nvim",
+ version = "3.5.1",
+ init = function()
+ require("core.utils").lazy_load "indent-blankline.nvim"
+ end,
+ opts = function()
+ return require("plugins.configs.others").blankline
+ end,
+ config = function(_, opts)
+ require("core.utils").load_mappings "blankline"
+ dofile(vim.g.base46_cache .. "blankline")
+ require("indent_blankline").setup(opts)
+ end,
+ },
+
+ {
+ "nvim-treesitter/nvim-treesitter",
+ init = function()
+ require("core.utils").lazy_load "nvim-treesitter"
+ end,
+ cmd = { "TSInstall", "TSBufEnable", "TSBufDisable", "TSModuleInfo" },
+ build = ":TSUpdate",
+ opts = function()
+ return require "plugins.configs.treesitter"
+ end,
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "syntax")
+ require("nvim-treesitter.configs").setup(opts)
+ end,
+ },
+
+ -- git stuff
+ {
+ "lewis6991/gitsigns.nvim",
+ ft = { "gitcommit", "diff" },
+ init = function()
+ -- load gitsigns only when a git file is opened
+ vim.api.nvim_create_autocmd({ "BufRead" }, {
+ group = vim.api.nvim_create_augroup("GitSignsLazyLoad", { clear = true }),
+ callback = function()
+ vim.fn.system("git -C " .. '"' .. vim.fn.expand "%:p:h" .. '"' .. " rev-parse")
+ if vim.v.shell_error == 0 then
+ vim.api.nvim_del_augroup_by_name "GitSignsLazyLoad"
+ vim.schedule(function()
+ require("lazy").load { plugins = { "gitsigns.nvim" } }
+ end)
+ end
+ end,
+ })
+ end,
+ opts = function()
+ return require("plugins.configs.others").gitsigns
+ end,
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "git")
+ require("gitsigns").setup(opts)
+ end,
+ },
+
+ -- lsp stuff
+ {
+ "williamboman/mason.nvim",
+ cmd = { "Mason", "MasonInstall", "MasonInstallAll", "MasonUpdate" },
+ opts = function()
+ return require "plugins.configs.mason"
+ end,
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "mason")
+ require("mason").setup(opts)
+
+ -- custom nvchad cmd to install all mason binaries listed
+ vim.api.nvim_create_user_command("MasonInstallAll", function()
+ vim.cmd("MasonInstall " .. table.concat(opts.ensure_installed, " "))
+ end, {})
+
+ vim.g.mason_binaries_list = opts.ensure_installed
+ end,
+ },
+
+ {
+ "neovim/nvim-lspconfig",
+ init = function()
+ require("core.utils").lazy_load "nvim-lspconfig"
+ end,
+ config = function()
+ require "plugins.configs.lspconfig"
+ end,
+ },
+
+ -- load luasnips + cmp related in insert mode only
+ {
+ "hrsh7th/nvim-cmp",
+ event = "InsertEnter",
+ dependencies = {
+ {
+ -- snippet plugin
+ "L3MON4D3/LuaSnip",
+ dependencies = "rafamadriz/friendly-snippets",
+ opts = { history = true, updateevents = "TextChanged,TextChangedI" },
+ config = function(_, opts)
+ require("plugins.configs.others").luasnip(opts)
+ end,
+ },
+
+ -- autopairing of (){}[] etc
+ {
+ "windwp/nvim-autopairs",
+ opts = {
+ fast_wrap = {},
+ disable_filetype = { "TelescopePrompt", "vim" },
+ },
+ config = function(_, opts)
+ require("nvim-autopairs").setup(opts)
+
+ -- setup cmp for autopairs
+ local cmp_autopairs = require "nvim-autopairs.completion.cmp"
+ require("cmp").event:on("confirm_done", cmp_autopairs.on_confirm_done())
+ end,
+ },
+
+ -- cmp sources plugins
+ {
+ "saadparwaiz1/cmp_luasnip",
+ "hrsh7th/cmp-nvim-lua",
+ "hrsh7th/cmp-nvim-lsp",
+ "hrsh7th/cmp-buffer",
+ "hrsh7th/cmp-path",
+ },
+ },
+ opts = function()
+ return require "plugins.configs.cmp"
+ end,
+ config = function(_, opts)
+ require("cmp").setup(opts)
+ end,
+ },
+
+ {
+ "numToStr/Comment.nvim",
+ keys = {
+ { "gcc", mode = "n", desc = "Comment toggle current line" },
+ { "gc", mode = { "n", "o" }, desc = "Comment toggle linewise" },
+ { "gc", mode = "x", desc = "Comment toggle linewise (visual)" },
+ { "gbc", mode = "n", desc = "Comment toggle current block" },
+ { "gb", mode = { "n", "o" }, desc = "Comment toggle blockwise" },
+ { "gb", mode = "x", desc = "Comment toggle blockwise (visual)" },
+ },
+ init = function()
+ require("core.utils").load_mappings "comment"
+ end,
+ config = function(_, opts)
+ require("Comment").setup(opts)
+ end,
+ },
+
+ -- file managing , picker etc
+ {
+ "nvim-tree/nvim-tree.lua",
+ cmd = { "NvimTreeToggle", "NvimTreeFocus" },
+ init = function()
+ require("core.utils").load_mappings "nvimtree"
+ end,
+ opts = function()
+ return require "plugins.configs.nvimtree"
+ end,
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "nvimtree")
+ require("nvim-tree").setup(opts)
+ end,
+ },
+
+ {
+ "nvim-telescope/telescope.nvim",
+ dependencies = { "nvim-treesitter/nvim-treesitter", { "nvim-telescope/telescope-fzf-native.nvim", build = "make" } },
+ cmd = "Telescope",
+ init = function()
+ require("core.utils").load_mappings "telescope"
+ end,
+ opts = function()
+ return require "plugins.configs.telescope"
+ end,
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "telescope")
+ local telescope = require "telescope"
+ telescope.setup(opts)
+
+ -- load extensions
+ for _, ext in ipairs(opts.extensions_list) do
+ telescope.load_extension(ext)
+ end
+ end,
+ },
+
+ -- Only load whichkey after all the gui
+ {
+ "folke/which-key.nvim",
+ keys = { "<leader>", "<c-r>", "<c-w>", '"', "'", "`", "c", "v", "g" },
+ init = function()
+ require("core.utils").load_mappings "whichkey"
+ end,
+ cmd = "WhichKey",
+ config = function(_, opts)
+ dofile(vim.g.base46_cache .. "whichkey")
+ require("which-key").setup(opts)
+ end,
+ },
+}
+
+local config = require("core.utils").load_config()
+
+if #config.plugins > 0 then
+ table.insert(default_plugins, { import = config.plugins })
+end
+
+require("lazy").setup(default_plugins, config.lazy_nvim)
diff --git a/ar/.config/NvChad/vim_cheatsheet.webp b/ar/.config/NvChad/vim_cheatsheet.webp
new file mode 100644
index 0000000..d8bf593
--- /dev/null
+++ b/ar/.config/NvChad/vim_cheatsheet.webp
Binary files differ
diff --git a/ar/.config/NvChad/vim_cheatsheet2.webp b/ar/.config/NvChad/vim_cheatsheet2.webp
new file mode 100644
index 0000000..b64e63c
--- /dev/null
+++ b/ar/.config/NvChad/vim_cheatsheet2.webp
Binary files differ
diff --git a/ar/.config/NvChad/vscode/easymotion-config.vim b/ar/.config/NvChad/vscode/easymotion-config.vim
new file mode 100644
index 0000000..89c1f90
--- /dev/null
+++ b/ar/.config/NvChad/vscode/easymotion-config.vim
@@ -0,0 +1,2 @@
+let g:EasyMotion_smartcase = 1
+nmap f <Plug>(easymotion-bd-f)
diff --git a/ar/.config/NvChad/vscode/plugins.lua b/ar/.config/NvChad/vscode/plugins.lua
new file mode 100644
index 0000000..920d80f
--- /dev/null
+++ b/ar/.config/NvChad/vscode/plugins.lua
@@ -0,0 +1,153 @@
+-- plugins.lua
+
+return {
+
+ -- Alpha (Dashboard)
+ {
+ "goolord/alpha-nvim",
+ lazy = true,
+ },
+
+ -- Auto Pairs
+ {
+ "windwp/nvim-autopairs",
+ },
+
+ -- Bufferline
+ {
+ "akinsho/bufferline.nvim",
+ dependencies = {
+ "nvim-tree/nvim-web-devicons",
+ },
+ },
+
+ -- Colorscheme
+ {
+ "folke/tokyonight.nvim",
+ },
+
+ -- Comments
+ {
+ "numToStr/Comment.nvim",
+ config = function()
+ require("Comment").setup()
+ end,
+ },
+
+ -- Easymotion (VScode)
+ {
+ "ChristianChiarulli/vscode-easymotion",
+ },
+
+ -- Git Integration
+ {
+ "lewis6991/gitsigns.nvim",
+ },
+
+ -- Hop (Better Navigation)
+ {
+ "phaazon/hop.nvim",
+ lazy = true,
+ },
+
+ -- Indentation Highlighting
+ {
+ "lukas-reineke/indent-blankline.nvim",
+ },
+
+ -- Rainbow Highlighting
+ {
+ "HiPhish/nvim-ts-rainbow2",
+ },
+
+ -- Lualine
+ {
+ "nvim-lualine/lualine.nvim",
+ dependencies = {
+ "nvim-tree/nvim-web-devicons",
+ },
+ },
+
+ -- Language Support
+ {
+ "VonHeikemen/lsp-zero.nvim",
+ lazy = true,
+ branch = "v1.x",
+ dependencies = {
+ -- LSP Support
+ { "neovim/nvim-lspconfig" }, -- Required
+ { "williamboman/mason.nvim" }, -- Optional
+ { "williamboman/mason-lspconfig.nvim" }, -- Optional
+
+ -- Autocompletion
+ { "hrsh7th/nvim-cmp" }, -- Required
+ { "hrsh7th/cmp-nvim-lsp" }, -- Required
+ { "hrsh7th/cmp-buffer" }, -- Optional
+ { "hrsh7th/cmp-path" }, -- Optional
+ { "saadparwaiz1/cmp_luasnip" }, -- Optional
+ { "hrsh7th/cmp-nvim-lua" }, -- Optional
+
+ -- Snippets
+ { "L3MON4D3/LuaSnip" }, -- Required
+ { "rafamadriz/friendly-snippets" }, -- Optional
+ },
+ },
+
+ -- Nvim-tree (File Explorer)
+ {
+ "nvim-tree/nvim-tree.lua",
+ lazy = true,
+ dependencies = {
+ "nvim-tree/nvim-web-devicons",
+ },
+ },
+
+ -- Nvim-Surround (Manipulating Surroundings)
+ {
+ "kylechui/nvim-surround",
+ config = function()
+ require("nvim-surround").setup {
+ -- Configuration here, or leave empty to use defaults
+ }
+ end,
+ },
+
+ -- -- Quick-Scope
+ -- {
+ -- "unblevable/quick-scope",
+ -- },
+
+ -- Telescope (Fuzzy Finder)
+ {
+ "nvim-telescope/telescope.nvim",
+ lazy = true,
+ dependencies = {
+ { "nvim-lua/plenary.nvim" },
+ },
+ },
+
+ -- Treesitter
+ {
+ "nvim-treesitter/nvim-treesitter",
+ },
+
+ -- Toggle Term
+ {
+ "akinsho/toggleterm.nvim",
+ config = true,
+ },
+
+ -- Undo-Tree
+ {
+ "jiaoshijie/undotree",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ },
+ },
+
+ -- Which-key
+ {
+ "folke/which-key.nvim",
+ lazy = true,
+ },
+}
diff --git a/ar/.config/NvChad/vscode/remap.lua b/ar/.config/NvChad/vscode/remap.lua
new file mode 100644
index 0000000..e0b47c4
--- /dev/null
+++ b/ar/.config/NvChad/vscode/remap.lua
@@ -0,0 +1,32 @@
+vim.keymap.set("n", "<leader>pv", vim.cmd.Ex)
+
+vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv")
+vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv")
+
+vim.keymap.set("n", "J", "mzJ`z")
+vim.keymap.set("n", "<C-d>", "<C-d>zz")
+vim.keymap.set("n", "<C-u>", "<C-u>zz")
+vim.keymap.set("n", "n", "nzzzv")
+vim.keymap.set("n", "N", "Nzzzv")
+
+-- greatest remap ever
+vim.keymap.set("x", "<leader>p", [["_dP]])
+
+-- next greatest remap ever : asbjornHaland
+vim.keymap.set({ "n", "v" }, "<leader>y", [["+y]])
+vim.keymap.set("n", "<leader>Y", [["+Y]])
+
+vim.keymap.set({ "n", "v" }, "<leader>d", [["_d]])
+
+-- This is going to get me cancelled
+vim.keymap.set("i", "jk", "<Esc>")
+
+vim.keymap.set("n", "Q", "<nop>")
+
+vim.keymap.set("n", "<C-k>", "<cmd>cnext<CR>zz")
+vim.keymap.set("n", "<C-j>", "<cmd>cprev<CR>zz")
+vim.keymap.set("n", "<leader>k", "<cmd>lnext<CR>zz")
+vim.keymap.set("n", "<leader>j", "<cmd>lprev<CR>zz")
+
+vim.keymap.set("n", "<leader>s", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]])
+vim.keymap.set("n", "<leader>x", "<cmd>!chmod +x %<CR>", { silent = true })
diff --git a/ar/.config/NvChad/vscode/settings.vim b/ar/.config/NvChad/vscode/settings.vim
new file mode 100644
index 0000000..c48a73c
--- /dev/null
+++ b/ar/.config/NvChad/vscode/settings.vim
@@ -0,0 +1,49 @@
+function! s:closeOtherEditors()
+ call VSCodeNotify('workbench.action.closeEditorsInOtherGroups')
+ call VSCodeNotify('workbench.action.closeOtherEditors')
+endfunction
+
+function! s:manageEditorSize(...)
+ let count = a:1
+ let to = a:2
+ for i in range(1, count ? count : 1)
+ call VSCodeNotify(to == 'increase' ? 'workbench.action.increaseViewSize' : 'workbench.action.decreaseViewSize')
+ endfor
+endfunction
+
+command! -bang Only if <q-bang> == '!' | call <SID>closeOtherEditors() | else | call VSCodeNotify('workbench.action.joinAllGroups') | endif
+
+nnoremap <silent> <C-a>= :<C-u>call VSCodeNotify('workbench.action.evenEditorWidths')<CR>
+xnoremap <silent> <C-a>= :<C-u>call VSCodeNotify('workbench.action.evenEditorWidths')<CR>
+nnoremap <silent> <C-x>_ :<C-u>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
+xnoremap <silent> <C-x>_ :<C-u>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
+
+" nnoremap <silent> <C-w>> :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" xnoremap <silent> <C-w>> :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" nnoremap <silent> <C-w>+ :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" xnoremap <silent> <C-w>+ :<C-u>call <SID>manageEditorSize(v:count, 'increase')<CR>
+" nnoremap <silent> <C-w>< :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+" xnoremap <silent> <C-w>< :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+" nnoremap <silent> <C-w>- :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+" xnoremap <silent> <C-w>- :<C-u>call <SID>manageEditorSize(v:count, 'decrease')<CR>
+
+" Better Navigation
+nnoremap <silent> <C-j> :call VSCodeNotify('workbench.action.navigateDown')<CR>
+xnoremap <silent> <C-j> :call VSCodeNotify('workbench.action.navigateDown')<CR>
+nnoremap <silent> <C-k> :call VSCodeNotify('workbench.action.navigateUp')<CR>
+xnoremap <silent> <C-k> :call VSCodeNotify('workbench.action.navigateUp')<CR>
+nnoremap <silent> <C-h> :call VSCodeNotify('workbench.action.navigateLeft')<CR>
+xnoremap <silent> <C-h> :call VSCodeNotify('workbench.action.navigateLeft')<CR>
+nnoremap <silent> <C-l> :call VSCodeNotify('workbench.action.navigateRight')<CR>
+xnoremap <silent> <C-l> :call VSCodeNotify('workbench.action.navigateRight')<CR>
+
+" Bind C-/ to vscode commentary since calling from vscode produces double comments due to multiple cursors
+xnoremap <silent> <C-/> :call Comment()<CR>
+nnoremap <silent> <C-/> :call Comment()<CR>
+
+nnoremap <silent> <C-w>_ :<C-u>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
+
+nnoremap <silent> <Space> :call VSCodeNotify('whichkey.show')<CR>
+xnoremap <silent> <Space> :call VSCodeNotify('whichkey.show')<CR>
+
+" nnoremap <silent> <C-n> :call VSCodeNotify('workbench.view.explorer')<CR> \ No newline at end of file
diff --git a/ar/.config/TheSiahxyz/after/queries/markdown/textobjects.scm b/ar/.config/TheSiahxyz/after/queries/markdown/textobjects.scm
new file mode 100644
index 0000000..2882dd2
--- /dev/null
+++ b/ar/.config/TheSiahxyz/after/queries/markdown/textobjects.scm
@@ -0,0 +1,3 @@
+;; extends
+
+(fenced_code_block (code_fence_content) @code_cell.inner) @code_cell.outer
diff --git a/ar/.config/TheSiahxyz/ftplugin/markdown.lua b/ar/.config/TheSiahxyz/ftplugin/markdown.lua
new file mode 100644
index 0000000..266bcc9
--- /dev/null
+++ b/ar/.config/TheSiahxyz/ftplugin/markdown.lua
@@ -0,0 +1,440 @@
+-- Function to check if the current file is in the Obsidian repository
+local function is_in_obsidian_repo()
+ local current_file_path = vim.fn.expand("%:p:h")
+ local user = os.getenv("USER") -- Get the current user's name from the environment variable
+ local obsidian_path = "/home/" .. user .. "/Private/repos/Obsidian/"
+
+ return string.find(current_file_path, obsidian_path) ~= nil
+end
+
+function BoldMe()
+ -- Get the start and end positions of the visual selection
+ local start_pos = vim.fn.getpos("'<")
+ local end_pos = vim.fn.getpos("'>")
+ local start_line, start_col = start_pos[2], start_pos[3]
+ local end_line, end_col = end_pos[2], end_pos[3]
+ local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
+
+ if start_line == end_line then
+ lines[1] = lines[1]:sub(1, start_col - 1)
+ .. "**"
+ .. lines[1]:sub(start_col, end_col)
+ .. "**"
+ .. lines[1]:sub(end_col + 1)
+ else
+ lines[1] = lines[1]:sub(1, start_col - 1) .. "**" .. lines[1]:sub(start_col)
+ lines[#lines] = lines[#lines]:sub(1, end_col) .. "**" .. lines[#lines]:sub(end_col + 1)
+ end
+ vim.api.nvim_buf_set_lines(0, start_line - 1, end_line, false, lines)
+end
+
+-- Function to fold all headings of a specific level
+local function set_foldmethod_expr()
+ -- These are lazyvim.org defaults but setting them just in case a file
+ -- doesn't have them set
+ if vim.fn.has("nvim-0.10") == 1 then
+ vim.opt.foldmethod = "expr"
+ vim.wo.foldexpr = "v:lua.require'thesiahxyz.utils.markdown'.foldexpr()"
+ vim.opt.foldtext = ""
+ else
+ vim.opt.foldmethod = "indent"
+ vim.wo.foldtext = "v:lua.require'thesiahxyz.utils.markdown'.foldexpr()"
+ end
+ vim.opt.foldlevel = 99
+end
+
+local function fold_headings_of_level(level)
+ -- Move to the top of the file
+ vim.cmd("normal! gg")
+ -- Get the total number of lines
+ local total_lines = vim.fn.line("$")
+ for line = 1, total_lines do
+ -- Get the content of the current line
+ local line_content = vim.fn.getline(line)
+ -- "^" -> Ensures the match is at the start of the line
+ -- string.rep("#", level) -> Creates a string with 'level' number of "#" characters
+ -- "%s" -> Matches any whitespace character after the "#" characters
+ -- So this will match `## `, `### `, `#### ` for example, which are markdown headings
+ if line_content:match("^" .. string.rep("#", level) .. "%s") then
+ -- Move the cursor to the current line
+ vim.fn.cursor(line, 1)
+ -- Fold the heading if it matches the level
+ if vim.fn.foldclosed(line) == -1 then
+ vim.cmd("normal! za")
+ end
+ end
+ end
+end
+
+local function fold_markdown_headings(levels)
+ set_foldmethod_expr()
+ -- I save the view to know where to jump back after folding
+ local saved_view = vim.fn.winsaveview()
+ for _, level in ipairs(levels) do
+ fold_headings_of_level(level)
+ end
+ vim.cmd("nohlsearch")
+ -- Restore the view to jump to where I was
+ vim.fn.winrestview(saved_view)
+end
+
+local function make_heading_content()
+ -- Get the total number of lines in the buffer
+ local total_lines = vim.api.nvim_buf_line_count(0)
+
+ -- Iterate through all lines
+ for line = 1, total_lines do
+ -- Get the content of the current line
+ local line_content = vim.api.nvim_buf_get_lines(0, line - 1, line, false)[1]
+ -- Match headings with at least two '#' characters
+ local heading = line_content:match("^(##+)%s(.+)")
+ if heading then
+ -- Extract the heading text
+ local heading_text = line_content:match("^#+%s(.+)")
+ if heading_text then
+ -- Create the content line with markdown link syntax
+ local content_line = string.format("%s [%s]()", heading, heading_text)
+ -- Replace the current line with the modified content
+ vim.api.nvim_buf_set_lines(0, line - 1, line, false, { content_line })
+ end
+ end
+ end
+ -- Notify the user that the headings have been updated
+ vim.notify("Headings transformed into content format", vim.log.levels.INFO)
+end
+
+-- Generate/update a Markdown TOC
+-- To generate the TOC I use the markdown-toc plugin
+-- https://github.com/jonschlinkert/markdown-toc
+-- And the markdown-toc plugin installed as a LazyExtra
+-- Function to update the Markdown TOC with customizable headings
+local function update_markdown_toc(heading2, heading3)
+ -- local path = vim.fn.expand("%") -- Expands the current file name to a full path
+ local path = vim.api.nvim_buf_get_name(0)
+ local bufnr = 0 -- The current buffer number, 0 references the current active buffer
+ -- Save the current view
+ -- If I don't do this, my folds are lost when I run this keymap
+ vim.cmd("mkview")
+ -- Retrieves all lines from the current buffer
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ local toc_exists = false -- Flag to check if TOC marker exists
+ local frontmatter_end = 0 -- To store the end line number of frontmatter
+ -- Check for frontmatter and TOC marker
+ for i, line in ipairs(lines) do
+ if i == 1 and line:match("^---$") then
+ -- Frontmatter start detected, now find the end
+ for j = i + 1, #lines do
+ if lines[j]:match("^---$") then
+ frontmatter_end = j
+ break
+ end
+ end
+ end
+ -- Checks for the TOC marker
+ if line:match("^%s*<!%-%-%s*toc%s*%-%->%s*$") then
+ toc_exists = true
+ break
+ end
+ end
+ -- Inserts H2 and H3 headings and <!-- toc --> at the appropriate position
+ if not toc_exists then
+ local insertion_line = 1 -- Default insertion point after first line
+ if frontmatter_end > 0 then
+ -- Find H1 after frontmatter
+ for i = frontmatter_end + 1, #lines do
+ if lines[i]:match("^#%s+") then
+ insertion_line = i + 1
+ break
+ end
+ end
+ else
+ -- Find H1 from the beginning
+ for i, line in ipairs(lines) do
+ if line:match("^#%s+") then
+ insertion_line = i + 1
+ break
+ end
+ end
+ end
+ -- Insert the specified headings and <!-- toc --> without blank lines
+ -- Insert the TOC inside a H2 and H3 heading right below the main H1 at the top lamw25wmal
+ vim.api.nvim_buf_set_lines(bufnr, insertion_line, insertion_line, false, { heading2, heading3, "<!-- toc -->" })
+ end
+ -- Silently save the file, in case TOC is being created for the first time
+ vim.cmd("silent write")
+ -- Silently run markdown-toc to update the TOC without displaying command output
+ -- vim.fn.system('markdown-toc -i "' .. path .. '"')
+ -- I want my bulletpoints to be created only as "-" so passing that option as
+ -- an argument according to the docs
+ -- https://github.com/jonschlinkert/markdown-toc?tab=readme-ov-file#optionsbullets
+ vim.fn.system('markdown-toc --bullets "-" -i "' .. path .. '"')
+ vim.cmd("edit!") -- Reloads the file to reflect the changes made by markdown-toc
+ vim.cmd("silent write") -- Silently save the file
+ vim.notify("TOC updated and file saved", vim.log.levels.INFO)
+ -- -- In case a cleanup is needed, leaving this old code here as a reference
+ -- -- I used this code before I implemented the frontmatter check
+ -- -- Moves the cursor to the top of the file
+ -- vim.api.nvim_win_set_cursor(bufnr, { 1, 0 })
+ -- -- Deletes leading blank lines from the top of the file
+ -- while true do
+ -- -- Retrieves the first line of the buffer
+ -- local line = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1]
+ -- -- Checks if the line is empty
+ -- if line == "" then
+ -- -- Deletes the line if it's empty
+ -- vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, {})
+ -- else
+ -- -- Breaks the loop if the line is not empty, indicating content or TOC marker
+ -- break
+ -- end
+ -- end
+ -- Restore the saved view (including folds)
+ vim.cmd("loadview")
+end
+
+-- this setting makes markdown auto-set the 80 text width limit when typing
+-- vim.cmd('set fo+=a')
+if is_in_obsidian_repo() then
+ vim.bo.textwidth = 175 -- No limit for Obsidian repository
+end
+
+vim.cmd("set conceallevel=0")
+vim.cmd("setlocal spell spelllang=en_us")
+vim.cmd("setlocal expandtab shiftwidth=4 softtabstop=4 autoindent")
+vim.cmd([[
+ " arrows
+ iabbrev >> →
+ iabbrev << ←
+ iabbrev ^^ ↑
+ iabbrev VV ↓
+]])
+
+-- Makrdown.nvim settings
+vim.g.vim_markdown_auto_insert_bullets = 0
+vim.g.vim_markdown_autowrite = 1
+vim.g.vim_markdown_conceal = 0
+vim.g.vim_markdown_conceal_code_blocks = 0
+vim.g.vim_markdown_folding_disabled = 0
+vim.g.vim_markdown_folding_level = 2
+vim.g.vim_markdown_folding_style_pythonic = 1
+vim.g.vim_markdown_follow_anchor = 1
+vim.g.vim_markdown_new_list_item_indent = 0
+vim.g.vim_markdown_no_extensions_in_markdown = 1
+vim.g.vim_markdown_toc_autofit = 1
+
+-- MarkdownPreview settings
+-- Get the BROWSER environment variable
+local browser = os.getenv("BROWSER")
+vim.g.mkdp_browser = browser or "/usr/bin/firefox"
+vim.g.mkdp_echo_preview_url = 0
+
+-- Save the cursor position globally to access it across different mappings
+_G.saved_positions = {}
+
+local wk = require("which-key")
+wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>ct", group = "Copy" },
+ { "<leader>i", group = "Image" },
+ { "<leader>m", group = "Markdown" },
+ { "<leader>mh", group = "Headings" },
+})
+
+-- bold
+vim.api.nvim_buf_set_keymap(
+ 0,
+ "v",
+ "<leader>mb",
+ ":<C-u>lua BoldMe()<CR>",
+ { noremap = true, silent = true, desc = "Bold selection" }
+)
+
+-- copy
+vim.keymap.set("n", "<leader>mcd", "4wvg$y", { desc = "Copy description" })
+
+-- heading
+vim.keymap.set("n", "<leader>mhi", function()
+ -- Save the current cursor position
+ local cursor_pos = vim.api.nvim_win_get_cursor(0)
+ -- I'm using [[ ]] to escape the special characters in a command
+ vim.cmd([[:g/\(^$\n\s*#\+\s.*\n^$\)/ .+1 s/^#\+\s/#&/]])
+ -- Restore the cursor position
+ vim.api.nvim_win_set_cursor(0, cursor_pos)
+ -- Clear search highlight
+ vim.cmd("nohlsearch")
+end, { desc = "Increase headings without confirmation" })
+
+vim.keymap.set("n", "<leader>mhd", function()
+ -- Save the current cursor position
+ local cursor_pos = vim.api.nvim_win_get_cursor(0)
+ -- I'm using [[ ]] to escape the special characters in a command
+ vim.cmd([[:g/^\s*#\{2,}\s/ s/^#\(#\+\s.*\)/\1/]])
+ -- Restore the cursor position
+ vim.api.nvim_win_set_cursor(0, cursor_pos)
+ -- Clear search highlight
+ vim.cmd("nohlsearch")
+end, { desc = "Decrease headings without confirmation" })
+local function make_heading_content()
+ -- Get the total number of lines in the buffer
+ local total_lines = vim.api.nvim_buf_line_count(0)
+
+ -- Iterate through all lines
+ for line = 1, total_lines do
+ -- Get the content of the current line
+ local line_content = vim.api.nvim_buf_get_lines(0, line - 1, line, false)[1]
+ -- Match headings with at least two '#' characters
+ local heading = line_content:match("^(##+)%s(.+)")
+ if heading then
+ -- Extract the heading text
+ local heading_text = line_content:match("^#+%s(.+)")
+ if heading_text then
+ -- Create the content line with markdown link syntax
+ local content_line = string.format("%s [%s]()", heading, heading_text)
+ -- Replace the current line with the modified content
+ vim.api.nvim_buf_set_lines(0, line - 1, line, false, { content_line })
+ end
+ end
+ end
+ -- Notify the user that the headings have been updated
+ vim.notify("Headings transformed into content format", vim.log.levels.INFO)
+end
+vim.keymap.set("n", "<leader>mhl", make_heading_content, { desc = "Make heading content" })
+
+-- line to list
+vim.keymap.set(
+ "n",
+ "<leader>ml",
+ "^I-<Space>[<Space>]<Space><Esc>^j",
+ { remap = true, silent = false, desc = "Make a line into a list" }
+)
+
+-- folding
+-- Keymap for unfolding markdown headings of level 2 or above
+-- Changed all the markdown folding and unfolding keymaps from <leader>mfj to
+-- zj, zk, zl, z; and zu respectively lamw25wmal
+vim.keymap.set("n", "zu", function()
+ -- vim.keymap.set("n", "<leader>mfu", function()
+ -- Reloads the file to reflect the changes
+ vim.cmd("edit!")
+ vim.cmd("normal! zR") -- Unfold all headings
+end, { desc = "Unfold all headings level 2 or above" })
+
+-- gk jummps to the markdown heading above and then folds it
+-- zi by default toggles folding, but I don't need it lamw25wmal
+vim.keymap.set("n", "zh", function()
+ -- Difference between normal and normal!
+ -- - `normal` executes the command and respects any mappings that might be defined.
+ -- - `normal!` executes the command in a "raw" mode, ignoring any mappings.
+ vim.cmd("normal gk")
+ -- This is to fold the line under the cursor
+ vim.cmd("normal! za")
+end, { desc = "Fold the heading cursor currently on" })
+
+-- Keymap for folding markdown headings of level 1 or above
+vim.keymap.set("n", "zj", function()
+ -- vim.keymap.set("n", "<leader>mfj", function()
+ -- Reloads the file to refresh folds, otherwise you have to re-open neovim
+ vim.cmd("edit!")
+ -- Unfold everything first or I had issues
+ vim.cmd("normal! zR")
+ fold_markdown_headings({ 6, 5, 4, 3, 2, 1 })
+end, { desc = "Fold all headings level 1 or above" })
+
+-- Keymap for folding markdown headings of level 2 or above
+-- I know, it reads like "madafaka" but "k" for me means "2"
+vim.keymap.set("n", "zk", function()
+ -- vim.keymap.set("n", "<leader>mfk", function()
+ -- Reloads the file to refresh folds, otherwise you have to re-open neovim
+ vim.cmd("edit!")
+ -- Unfold everything first or I had issues
+ vim.cmd("normal! zR")
+ fold_markdown_headings({ 6, 5, 4, 3, 2 })
+end, { desc = "Fold all headings level 2 or above" })
+
+-- Keymap for folding markdown headings of level 3 or above
+vim.keymap.set("n", "zl", function()
+ -- vim.keymap.set("n", "<leader>mfl", function()
+ -- Reloads the file to refresh folds, otherwise you have to re-open neovim
+ vim.cmd("edit!")
+ -- Unfold everything first or I had issues
+ vim.cmd("normal! zR")
+ fold_markdown_headings({ 6, 5, 4, 3 })
+end, { desc = "Fold all headings level 3 or above" })
+
+-- Keymap for folding markdown headings of level 4 or above
+vim.keymap.set("n", "z;", function()
+ -- vim.keymap.set("n", "<leader>mf;", function()
+ -- Reloads the file to refresh folds, otherwise you have to re-open neovim
+ vim.cmd("edit!")
+ -- Unfold everything first or I had issues
+ vim.cmd("normal! zR")
+ fold_markdown_headings({ 6, 5, 4 })
+end, { desc = "Fold all headings level 4 or above" })
+
+-- link text objects for i and a
+vim.keymap.set(
+ { "o", "x" },
+ "il",
+ "<cmd>lua require('various-textobjs').mdlink('inner')<CR>",
+ { buffer = true, desc = "Link" }
+)
+vim.keymap.set(
+ { "o", "x" },
+ "al",
+ "<cmd>lua require('various-textobjs').mdlink('outer')<CR>",
+ { buffer = true, desc = "Link" }
+)
+
+-- preview
+vim.keymap.set("n", "<leader>mp", ":MarkdownPreview<CR>", { desc = "Markdown preview" })
+
+-- traversal
+vim.keymap.set("n", "<Tab>", "<Plug>Markdown_Fold", { desc = "Tab is for moving around only" })
+
+vim.keymap.set("n", "]]", "<Plug>Markdown_MoveToNextHeader", { desc = "Go to next header" })
+vim.keymap.set("n", "[[", "<Plug>Markdown_MoveToPreviousHeader", { desc = "Go to previous header. Contrast with ]c" })
+vim.keymap.set("n", "][", "<Plug>Markdown_MoveToNextSiblingHeader", { desc = "Go to next sibling header if any" })
+vim.keymap.set(
+ "n",
+ "[]",
+ "<Plug>Markdown_MoveToPreviousSiblingHeader",
+ { desc = "Go to previous sibling header if any" }
+)
+vim.keymap.set("n", "]c", "<Plug>Markdown_MoveToCurHeader", { desc = "Go to Current header" })
+vim.keymap.set("n", "]u", "<Plug>Markdown_MoveToParentHeader", { desc = "Go to parent header (Up)" })
+vim.keymap.set("n", "<C-]>", "<Plug>Markdown_Checkbox", { desc = "Toggle checkboxes" })
+vim.keymap.set("n", "Tab", "<Plug>Markdown_Fold", { desc = "Fold headers/lists" })
+vim.keymap.set("n", "Return", "<Plug>Markdown_FollowLink", { desc = "Follow links" })
+vim.keymap.set("i", "Tab", "<Plug>Markdown_Jump", { desc = "Indent new bullets, jump through empty fields in links" })
+vim.keymap.set({ "i", "v" }, "<C-\\>", "<Plug>Markdown_CreateLink", { desc = "Create new links" })
+vim.keymap.set("i", "<C-o>", "<Plug>Markdown_NewLineAbove", { desc = "New line above, overrides default" })
+vim.keymap.set("i", "<C-b>", "<Plug>Markdown_NewLineBelow", { desc = "New line below, overrides default" })
+vim.keymap.set("i", "Return", "<Plug>Markdown_NewLineBelow", { desc = "New line below, overrides default" })
+-- jump to the first line of the TOC
+vim.keymap.set("n", "<leader>mm", function()
+ -- Save the current cursor position
+ _G.saved_positions["toc_return"] = vim.api.nvim_win_get_cursor(0)
+ -- Perform a silent search for the <!-- toc --> marker and move the cursor two lines below it
+ vim.cmd("silent! /<!-- toc -->\\n\\n\\zs.*")
+ -- Clear the search highlight without showing the "search hit BOTTOM, continuing at TOP" message
+ vim.cmd("nohlsearch")
+ -- Retrieve the current cursor position (after moving to the TOC)
+ local cursor_pos = vim.api.nvim_win_get_cursor(0)
+ local row = cursor_pos[1]
+ -- local col = cursor_pos[2]
+ -- Move the cursor to column 15 (starts counting at 0)
+ -- I like just going down on the TOC and press gd to go to a section
+ vim.api.nvim_win_set_cursor(0, { row, 14 })
+end, { desc = "Jump to the first line of the TOC" })
+-- return to the previously saved cursor position
+vim.keymap.set("n", "<leader>mn", function()
+ local pos = _G.saved_positions["toc_return"]
+ if pos then
+ vim.api.nvim_win_set_cursor(0, pos)
+ end
+end, { desc = "Return to position before jumping" })
+
+-- TOC (english)
+vim.keymap.set("n", "<leader>mt", function()
+ update_markdown_toc("## Contents", "### Table of contents")
+end, { desc = "Insert/update Markdown TOC (English)" })
diff --git a/ar/.config/TheSiahxyz/ftplugin/quarto.lua b/ar/.config/TheSiahxyz/ftplugin/quarto.lua
new file mode 100644
index 0000000..92e0025
--- /dev/null
+++ b/ar/.config/TheSiahxyz/ftplugin/quarto.lua
@@ -0,0 +1,6 @@
+-- set some options for quarto files, but they're set on the window, so they'll apply to other
+-- buffers too
+vim.wo.linebreak = true
+vim.wo.breakindent = true
+vim.wo.showbreak = "|"
+vim.wo.conceallevel = 0
diff --git a/ar/.config/TheSiahxyz/init.lua b/ar/.config/TheSiahxyz/init.lua
new file mode 100644
index 0000000..caa926e
--- /dev/null
+++ b/ar/.config/TheSiahxyz/init.lua
@@ -0,0 +1 @@
+require("thesiahxyz")
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/core/autocmds.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/autocmds.lua
new file mode 100644
index 0000000..10c1085
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/autocmds.lua
@@ -0,0 +1,381 @@
+local function augroup(name)
+ return vim.api.nvim_create_augroup("TheSiahxyz_" .. name, { clear = true })
+end
+
+local autocmd = vim.api.nvim_create_autocmd
+
+-- Change the color of symlink files to distinguish between directory and symlink files
+autocmd("FileType", {
+ group = augroup("highlight_linked_files"),
+ pattern = "netrw",
+ callback = function()
+ vim.cmd("highlight NetrwSymlink guifg=#689D6A ctermfg=214")
+ end,
+})
+
+-- Check if we need to reload the file when it changed
+autocmd({ "FocusGained", "TermClose", "TermLeave" }, {
+ group = augroup("check_time"),
+ callback = function()
+ if vim.o.buftype ~= "nofile" then
+ vim.cmd("checktime")
+ end
+ end,
+})
+
+-- Highlight on yank
+autocmd("TextYankPost", {
+ group = augroup("highlight_yank"),
+ callback = function()
+ vim.highlight.on_yank()
+ end,
+})
+
+-- resize splits if window got resized
+autocmd({ "VimResized" }, {
+ group = augroup("window_config"),
+ callback = function()
+ local current_tab = vim.fn.tabpagenr()
+ vim.cmd("tabdo wincmd =")
+ vim.cmd("tabnext " .. current_tab)
+ end,
+})
+
+-- go to last loc when opening a buffer
+autocmd("BufReadPost", {
+ group = augroup("last_loc"),
+ callback = function(event)
+ local exclude = { "gitcommit" }
+ local buf = event.buf
+ if vim.tbl_contains(exclude, vim.bo[buf].filetype) or vim.b[buf].thesiahxyz_last_loc then
+ return
+ end
+ vim.b[buf].thesiahxyz_last_loc = true
+ local mark = vim.api.nvim_buf_get_mark(buf, '"')
+ local lcount = vim.api.nvim_buf_line_count(buf)
+ if mark[1] > 0 and mark[1] <= lcount then
+ pcall(vim.api.nvim_win_set_cursor, 0, mark)
+ end
+ end,
+})
+
+-- close some filetypes with <q>
+autocmd("FileType", {
+ group = augroup("close_with_q"),
+ pattern = {
+ "checkhealth",
+ "help",
+ "lspinfo",
+ "neotest-output",
+ "neotest-output-panel",
+ "neotest-summary",
+ "notify",
+ "PlenaryTestPopup",
+ "qf",
+ "query",
+ "spectre_panel",
+ "startuptime",
+ "terminal",
+ "tsplayground",
+ },
+ callback = function(event)
+ vim.bo[event.buf].buflisted = false
+ vim.keymap.set("n", "q", "<cmd>close<cr>", { buffer = event.buf, silent = true })
+ end,
+})
+autocmd("FileType", {
+ group = augroup("q_as_bd"),
+ pattern = "netrw",
+ callback = function(event)
+ vim.bo[event.buf].buflisted = false
+ vim.keymap.set("n", "q", function()
+ vim.cmd("bd")
+ end, { buffer = event.buf, silent = true })
+ end,
+})
+
+-- Start insert mode in terminal
+autocmd("TermOpen", {
+ group = augroup("terminal"),
+ pattern = "*",
+ callback = function()
+ vim.cmd("startinsert")
+ end,
+})
+
+-- Make it easier to close man-files when opened inline
+autocmd("FileType", {
+ group = augroup("man_close"),
+ pattern = { "man" },
+ callback = function(event)
+ vim.bo[event.buf].buflisted = false
+ end,
+})
+
+-- Wrap and check for spell in text filetypes
+autocmd("FileType", {
+ group = augroup("wrap_spell"),
+ pattern = { "text", "plaintex", "typst", "gitcommit", "markdown" },
+ callback = function()
+ vim.opt_local.wrap = true
+ vim.opt_local.spell = true
+ vim.opt_local.spelllang = { "en", "cjk" }
+ vim.opt_local.spellsuggest = { "best", "9" }
+ end,
+})
+
+-- Fix conceallevel for json files
+autocmd({ "FileType" }, {
+ group = augroup("json_config"),
+ pattern = { "json", "jsonc", "json5" },
+ callback = function()
+ vim.opt_local.conceallevel = 0
+ end,
+})
+
+-- Auto create dir when saving a file, in case some intermediate directory does not exist
+autocmd({ "BufWritePre" }, {
+ group = augroup("create_dir"),
+ callback = function(event)
+ if event.match:match("^%w%w+:[\\/][\\/]") then
+ return
+ end
+ local file = vim.uv.fs_realpath(event.match) or event.match
+ vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p")
+ end,
+})
+
+-- Automatically delete all trailing whitespace and newlines at end of file on save
+local file_save = augroup("file_save")
+autocmd("BufWritePre", {
+ group = file_save,
+ pattern = "*",
+ callback = function()
+ -- Remove trailing spaces
+ vim.cmd([[ %s/\s\+$//e ]])
+ -- Remove trailing newlines
+ vim.cmd([[ %s/\n\+\%$//e ]])
+ end,
+})
+
+-- Add a trailing newline for C files
+autocmd("BufWritePre", {
+ group = file_save,
+ pattern = "*.[ch]",
+ callback = function()
+ vim.cmd([[ %s/\%$/\r/e ]])
+ end,
+})
+
+-- Correct email signature delimiter in neomutt files
+autocmd("BufWritePre", {
+ group = file_save,
+ pattern = "*neomutt*",
+ callback = function()
+ vim.cmd([[ %s/^--$/-- /e ]])
+ end,
+})
+
+local function organize_imports()
+ local params = {
+ command = "pyright.organizeimports",
+ arguments = { vim.uri_from_bufnr(0) },
+ }
+ vim.lsp.buf.execute_command(params)
+end
+
+autocmd("LspAttach", {
+ group = augroup("lsp_attach"),
+ callback = function(e)
+ local client = vim.lsp.get_client_by_id(e.data.client_id)
+ if client and client.name == "pyright" then
+ vim.api.nvim_create_user_command(
+ "PyrightOrganizeImports",
+ organize_imports,
+ { desc = "Organize imports (lsp)" }
+ )
+ end
+ vim.keymap.set("n", "gD", function()
+ vim.lsp.buf.definition()
+ end, { buffer = e.buf, desc = "Go to definition (lsp)" })
+ vim.keymap.set("n", "K", function()
+ vim.lsp.buf.hover()
+ end, { buffer = e.buf, desc = "Go to keywords (lsp)" })
+ vim.keymap.set("n", "<leader>ls", function()
+ vim.lsp.buf.workspace_symbol()
+ end, { buffer = e.buf, desc = "Workspace symbol (lsp)" })
+ vim.keymap.set("n", "<leader>lo", function()
+ vim.diagnostic.open_float()
+ end, { buffer = e.buf, desc = "Open diagnostic" })
+ vim.keymap.set("n", "<leader>lc", function()
+ vim.lsp.buf.code_action()
+ end, { buffer = e.buf, desc = "Code action (lsp)" })
+ vim.keymap.set("n", "<leader>lr", function()
+ vim.lsp.buf.references()
+ end, { buffer = e.buf, desc = "References (lsp)" })
+ vim.keymap.set("n", "<leader>ln", function()
+ vim.lsp.buf.rename()
+ end, { buffer = e.buf, desc = "Rename (lsp)" })
+ vim.keymap.set("n", "<leader>lh", function()
+ vim.lsp.buf.signature_help()
+ end, { buffer = e.buf, desc = "Signature help (lsp)" })
+ vim.keymap.set("n", "]d", function()
+ vim.diagnostic.goto_next()
+ end, { buffer = e.buf, desc = "Next diagnostic" })
+ vim.keymap.set("n", "[d", function()
+ vim.diagnostic.goto_prev()
+ end, { buffer = e.buf, desc = "Previous diagnostic" })
+ end,
+})
+
+-- Save file as sudo on files that require root permission
+vim.api.nvim_create_user_command("SudoWrite", function()
+ vim.cmd([[
+ write !sudo tee % 2>/dev/null
+ edit!
+ ]])
+end, {})
+vim.api.nvim_create_user_command("SudoWritequit", function()
+ vim.cmd([[
+ write !sudo tee % 2>/dev/null
+ edit!
+ quit!
+ ]])
+end, {})
+
+-- Markdown for specific files and directories
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "/tmp/calcurse*", "~/.calcurse/notes/*" },
+ command = "set filetype=markdown",
+})
+
+-- Groff for specific file extensions
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "*.ms", "*.me", "*.mom", "*.man" },
+ callback = function()
+ vim.cmd([[
+ set columns=90
+ set filetype=groff
+ set linebreak
+ set textwidth=0
+ set wrap
+ set wrapmargin=0
+ ]])
+ end,
+})
+
+-- TeX for .tex files
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ pattern = { "*.tex" },
+ command = "set filetype=tex",
+})
+
+-- When shortcut files are updated, renew bash and lf configs with new material:
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = augroup("bookmarks"),
+ pattern = { "bm-files", "bm-dirs" },
+ callback = function()
+ -- Execute the 'shortcuts' shell command
+ local result = vim.fn.system("shortcuts")
+ -- Display an error message only if the 'shortcuts' command fails
+ if vim.v.shell_error ~= 0 then
+ -- Display an error message if the 'shortcuts' command fails
+ vim.api.nvim_echo({ { "failed to update shortcuts: " .. result, "ErrorMsg" } }, true, {})
+ end
+ end,
+})
+
+-- Source lfrc if it's edited
+local lf_config = augroup("lf_config")
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = lf_config,
+ pattern = { "lfrc" },
+ callback = function()
+ vim.cmd("silent !source ~/.config/lf/lfrc")
+ end,
+})
+
+-- Run xrdb whenever Xdefaults or Xresources are updated.
+local x_config = augroup("x_config")
+vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ group = x_config,
+ pattern = { "Xresources", "Xdefaults", "xresources", "xdefaults" },
+ callback = function()
+ vim.bo.filetype = "xdefaults"
+ end,
+})
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = x_config,
+ pattern = { "Xresources", "Xdefaults", "xresources", "xdefaults" },
+ callback = function()
+ vim.cmd("silent !xrdb %")
+ end,
+})
+
+-- Recompile suckless programs on config edit.
+local home = os.getenv("HOME")
+local suckless_config = vim.api.nvim_create_augroup("suckless_config", { clear = true })
+
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = suckless_config,
+ pattern = home .. "/.local/src/suckless/dmenu/config.h",
+ callback = function()
+ vim.cmd("silent !cd " .. home .. "/.local/src/suckless/dmenu/ && sudo make install")
+ end,
+})
+
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = suckless_config,
+ pattern = home .. "/.local/src/suckless/dwmblocks/config.h",
+ callback = function()
+ vim.cmd(
+ "silent !cd "
+ .. home
+ .. "/.local/src/suckless/dwmblocks/ && sudo make install && { killall -q dwmblocks; setsid -f dwmblocks; }"
+ )
+ end,
+})
+
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = suckless_config,
+ pattern = home .. "/.local/src/suckless/slock/config.h",
+ callback = function()
+ vim.cmd("silent !cd " .. home .. "/.local/src/suckless/slock/ && sudo make install")
+ end,
+})
+
+local suckless_keys = augroup("suckless_keys")
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = suckless_keys,
+ pattern = home .. "/.local/src/suckless/dwm/config.h",
+ callback = function()
+ vim.cmd("silent !" .. home .. "/.local/bin/extractkeys")
+ end,
+})
+
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = suckless_keys,
+ pattern = home .. "/.local/src/suckless/st/config.h",
+ callback = function()
+ vim.cmd("silent !" .. home .. "/.local/bin/extractkeys")
+ end,
+})
+
+vim.api.nvim_create_autocmd("BufWritePost", {
+ group = augroup("suckless_doc"),
+ pattern = home .. "/.local/src/suckless/dwm/thesiah-default.mom",
+ callback = function()
+ vim.cmd("silent !cd " .. home .. "/.local/src/suckless/dwm/ && rm -f thesiah.mom")
+ end,
+})
+
+vim.api.nvim_create_autocmd({ "BufRead", "BufEnter" }, {
+ pattern = { "*.c", "*.cpp", "*.h", "*.hpp" },
+ callback = function()
+ local suckless_path = vim.fn.expand("~/.local/src/suckless"):gsub("/+$", "")
+ local file_path = vim.fn.expand("%:p"):gsub("/+$", "")
+ if file_path:sub(1, #suckless_path) == suckless_path then
+ vim.b.autoformat = false
+ end
+ end,
+})
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/core/keymaps.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/keymaps.lua
new file mode 100644
index 0000000..162b71a
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/keymaps.lua
@@ -0,0 +1,806 @@
+-- Init leader Keys
+vim.g.mapleader = " "
+vim.g.maplocalleader = "\\"
+
+-- Ascii
+vim.keymap.set("n", "<leader>a1", ":.!toilet -w 200 -f bfraktur<cr>", { desc = "Ascii art bfraktur" })
+vim.keymap.set("n", "<leader>a2", ":.!toilet -w 200 -f emboss<cr>", { desc = "Ascii art emboss" })
+vim.keymap.set("n", "<leader>a3", ":.!toilet -w 200 -f emboss2<cr>", { desc = "Ascii art emboss2" })
+vim.keymap.set("n", "<leader>a4", ":.!toilet -w 200 -f future<cr>", { desc = "Ascii art future" })
+vim.keymap.set("n", "<leader>a5", ":.!toilet -w 200 -f pagga<cr>", { desc = "Ascii art pagga" })
+vim.keymap.set("n", "<leader>a6", ":.!toilet -w 200 -f wideterm<cr>", { desc = "Ascii art wideterm" })
+vim.keymap.set("n", "<leader>a7", ":.!figlet -w 200 -f standard<cr>", { desc = "Ascii art standard" })
+vim.keymap.set("n", "<leader>a8", ":.!figlet -w 200 -f slant<cr>", { desc = "Ascii art slant" })
+vim.keymap.set("n", "<leader>a9", ":.!figlet -w 200 -f big<cr>", { desc = "Ascii art big" })
+vim.keymap.set("n", "<leader>a0", ":.!figlet -w 200 -f shadow<cr>", { desc = "Ascii art shadow" })
+
+-- Buffers
+vim.keymap.set({ "n", "v", "x", "t" }, "<A-x>", "<cmd>bd!<cr>", { desc = "Delete buffer" })
+vim.keymap.set({ "i", "n", "t" }, "<C-p>", "<cmd>bprevious<cr>", { desc = "Previous buffer" })
+vim.keymap.set({ "i", "n", "t" }, "<C-n>", "<cmd>bnext<cr>", { desc = "Next buffer" })
+vim.keymap.set({ "n", "t" }, "<S-h>", "<cmd>bprevious<cr>", { desc = "Previous buffer" })
+vim.keymap.set({ "n", "t" }, "<S-l>", "<cmd>bnext<cr>", { desc = "Next buffer" })
+vim.keymap.set("n", "<leader><leader>", "<cmd>e #<cr>", { desc = "Switch to last buffer" })
+vim.keymap.set({ "n", "v", "x", "t" }, "<leader>bd", "<cmd>:bd<cr>", { desc = "Close buffer" })
+vim.keymap.set({ "n", "v", "x", "t" }, "<leader>BD", "<cmd>:bd!<cr>", { desc = "Force close buffer" })
+vim.keymap.set("n", "<leader>bn", "<cmd>enew<cr>", { desc = "New buffer" })
+vim.keymap.set({ "i", "x", "n", "s" }, "<C-s>", "<cmd>w<cr><esc>", { desc = "Save current buffer" })
+vim.keymap.set({ "n", "v" }, "<leader>wq", "<cmd>wq<cr>", { desc = "Save current buffer and quit" })
+vim.keymap.set({ "n", "v" }, "<leader>WQ", "<cmd>wqa<cr>", { desc = "Save all buffers and quit" })
+vim.keymap.set({ "n", "v" }, "<leader>qq", "<cmd>q!<cr>", { desc = "Force quit" })
+vim.keymap.set({ "n", "v" }, "<leader>QQ", "<cmd>qa!<cr>", { desc = "Force quit all" })
+vim.keymap.set("n", "<leader>rn", function()
+ local current_name = vim.fn.expand("%:p") -- Get the full path of the current file
+ local directory = vim.fn.expand("%:p:h") -- Get the directory of the current file
+ local new_name = vim.fn.input("New filename: ", directory .. "/") -- Prompt for the new name
+ if new_name == "" or new_name == current_name then
+ return -- Do nothing if no input or name hasn't changed
+ end
+ vim.cmd("silent !mv " .. vim.fn.shellescape(current_name) .. " " .. vim.fn.shellescape(new_name))
+ vim.cmd("edit " .. vim.fn.fnameescape(new_name))
+ vim.cmd("bdelete " .. vim.fn.bufnr(current_name))
+end, { noremap = true, silent = true, desc = "Rename file" })
+vim.keymap.set("n", "<leader>ms", function()
+ vim.cmd("new | put=execute('messages') | setlocal buftype=nofile")
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
+ local filtered_lines = {}
+ for _, line in ipairs(lines) do
+ local trimmed_line = line:match("^%s*(.-)%s*$") -- Remove leading and trailing whitespace
+ if trimmed_line ~= "" then
+ table.insert(filtered_lines, trimmed_line) -- Only add non-empty lines
+ end
+ end
+ vim.fn.setreg('"', table.concat(filtered_lines, "\n"))
+ vim.api.nvim_buf_set_lines(buf, 0, -1, false, filtered_lines)
+ vim.keymap.set("n", "q", function()
+ vim.api.nvim_buf_delete(buf, { force = true })
+ end, { buffer = buf, desc = "Close message buffer" })
+end, { desc = "Open messages, trim, and copy content" })
+
+-- Cd
+vim.keymap.set("n", "gcd", ":cd %:p:h<cr>:pwd<cr>", { desc = "Go to current file path" })
+vim.keymap.set("n", "gcD", function()
+ local realpath = vim.fn.systemlist("readlink -f " .. vim.fn.shellescape(vim.fn.expand("%:p")))[1]
+ vim.cmd("cd " .. vim.fn.fnameescape(vim.fn.fnamemodify(realpath, ":h")))
+ vim.cmd("pwd")
+end, { desc = "Go to real path of current file" })
+vim.keymap.set("n", "gc.", ":cd ..<cr>:pwd<cr>", { desc = "Go to current file path" })
+
+-- Check health
+vim.keymap.set("n", "<leader>ch", ":checkhealth<cr>", { desc = "Check neovim health" })
+
+-- Clear search with <esc>
+vim.keymap.set({ "i", "n" }, "<esc>", "<cmd>noh<cr><esc>", { desc = "Clear search highlights" })
+
+-- Clear search / diff update / redraw
+vim.keymap.set(
+ "n",
+ "<leader>R",
+ "<cmd>nohlsearch<bar>diffupdate<bar>normal! <C-l><cr>",
+ { desc = "Redraw / Clear hlsearch / Diff Update" }
+)
+
+-- Copy
+vim.keymap.set("n", "<leader>cp", function()
+ local filepath = vim.fn.expand("%") -- Get the current filepath
+ local filename = vim.fn.expand("%:t") -- Get the current filename
+ local file_root = vim.fn.expand("%:r") -- Get the file root (without extension)
+ local file_ext = vim.fn.expand("%:e") -- Get the file extension
+ local new_filename = file_root .. "_copied" -- Start with the base for new filename
+ local num = 1
+ while vim.fn.filereadable(new_filename .. "." .. file_ext) == 1 do
+ new_filename = file_root .. "_copied_" .. num
+ num = num + 1
+ end
+ new_filename = new_filename .. "." .. file_ext
+ local cmd = "cp " .. filepath .. " " .. new_filename
+
+ -- Wrap input in pcall to handle Ctrl-c interruptions
+ local ok, confirm = pcall(vim.fn.input, 'Do you want to copy "' .. filename .. '"? (y/n): ')
+
+ -- If interrupted (Ctrl-c), return silently
+ if not ok or confirm == nil or confirm == "" then
+ return
+ end
+
+ -- Handle positive confirmation
+ if confirm:lower() == "y" or confirm:lower() == "yes" then
+ vim.cmd("silent !" .. cmd)
+ vim.cmd("silent edit " .. new_filename)
+ end
+end, { silent = true, desc = "Copy current file" })
+vim.keymap.set(
+ "n",
+ "<leader>cP",
+ ':let @+ = expand("%:p")<cr>:lua print("Copied path to: " .. vim.fn.expand("%:p"))<cr>',
+ { desc = "Copy current file name and path" }
+)
+
+-- Cut, Yank & Paste
+vim.keymap.set({ "n", "v" }, "<leader>y", [["+y]], { desc = "Yank to clipboard" })
+vim.keymap.set("n", "<leader>Y", [["+Y]], { desc = "Yank line to clipboard" })
+vim.keymap.set({ "n", "v" }, "<leader><C-y>", ":%y<cr>", { desc = "Yank current file to clipboard" })
+vim.keymap.set({ "n", "v", "x" }, "<leader>pp", [["+p]], { desc = "Paste from clipboard after cursor" })
+vim.keymap.set({ "n", "v", "x" }, "<leader>PP", [["+P]], { desc = "Paste from clipboard before cursor" })
+vim.keymap.set({ "n", "v", "x" }, "<leader>pP", [["_dP]], { desc = "Paste over and preserve clipboard" })
+vim.keymap.set({ "n", "v" }, "<leader>dd", [["+d]], { desc = "Delete and yank to clipboard" })
+vim.keymap.set({ "n", "v" }, "<leader>DD", [["_d]], { desc = "Delete without storing in clipboard" })
+vim.keymap.set("n", "<leader><C-d>", ":%d_<cr>", { desc = "Delete all to black hole register" })
+
+-- Diagnostic
+local diagnostic_goto = function(next, severity)
+ local go = next and vim.diagnostic.goto_next or vim.diagnostic.goto_prev
+ severity = severity and vim.diagnostic.severity[severity] or nil
+ return function()
+ go({ severity = severity })
+ end
+end
+-- vim.keymap.set("n", "[d", diagnostic_goto(false), { desc = "Previous diagnostic" })
+-- vim.keymap.set("n", "]d", diagnostic_goto(true), { desc = "Next diagnostic" })
+vim.keymap.set("n", "<leader>[e", diagnostic_goto(false, "ERROR"), { desc = "Previous error" })
+vim.keymap.set("n", "<leader>]e", diagnostic_goto(true, "ERROR"), { desc = "Next error" })
+vim.keymap.set("n", "<leader>[w", diagnostic_goto(false, "WARN"), { desc = "Previous warning" })
+vim.keymap.set("n", "<leader>]w", diagnostic_goto(true, "WARN"), { desc = "Next warning" })
+vim.keymap.set("n", "<leader>od", vim.diagnostic.open_float, { desc = "Open diagnostic message" })
+vim.keymap.set("n", "<leader>la", vim.diagnostic.setloclist, { desc = "Add diagnostics to location list" })
+
+-- Disable
+vim.keymap.set("n", "Q", "<nop>", { desc = "Disable q command" })
+
+-- Explore
+vim.keymap.set("n", "<leader>ex", vim.cmd.Ex, { desc = "Open explorer" })
+vim.keymap.set("n", "<leader>es", vim.cmd.Sex, { desc = "Open explorer (horizontal split)" })
+vim.keymap.set("n", "<leader>ev", vim.cmd.Vex, { desc = "Open explorer (vertical split)" })
+vim.keymap.set("n", "<leader>qe", function()
+ if vim.bo.filetype == "netrw" then
+ vim.cmd("bd")
+ end
+end, { desc = "Close explorer (netrw)" })
+
+-- Files
+vim.keymap.set("n", "<leader>fn", "<cmd>enew<cr>", { desc = "Open new buffer" })
+
+-- Fix List & Trouble
+vim.keymap.set("n", "[o", "<cmd>lprev<cr>zz", { desc = "Previous location" })
+vim.keymap.set("n", "]o", "<cmd>lnext<cr>zz", { desc = "Next location" })
+vim.keymap.set("n", "[q", "<cmd>cprev<cr>zz", { desc = "Previous quickfix" })
+vim.keymap.set("n", "]q", "<cmd>cnext<cr>zz", { desc = "Next quickfix" })
+vim.keymap.set(
+ "n",
+ "<leader>rw",
+ [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]],
+ { desc = "Search and replace word under cursor" }
+)
+vim.keymap.set("n", "<leader>oo", "<cmd>lopen<cr>", { desc = "Open location list" })
+vim.keymap.set("n", "<leader>oq", "<cmd>copen<cr>", { desc = "Open quickfix list" })
+
+-- Formats
+vim.keymap.set("n", "<leader>cF", vim.lsp.buf.format, { desc = "Format buffer by default lsp" })
+
+-- Git
+-- create repository
+vim.keymap.set("n", "<leader>gR", function()
+ -- Check if GitHub CLI is installed
+ local gh_installed = vim.fn.system("command -v gh")
+ if gh_installed == "" then
+ print("GitHub CLI is not installed. Please install it using 'brew install gh'.")
+ return
+ end
+ -- Get the current working directory and extract the repository name
+ local cwd = vim.fn.getcwd()
+ local repo_name = vim.fn.fnamemodify(cwd, ":t")
+ if repo_name == "" then
+ print("Failed to extract repository name from the current directory.")
+ return
+ end
+ -- Display the message and ask for confirmation
+ local confirmation = vim.fn.input('The name of the repo will be: "' .. repo_name .. '"\nType "yes" to continue: ')
+ if confirmation:lower() ~= "yes" then
+ print("Operation canceled.")
+ return
+ end
+ -- Check if the repository already exists on GitHub
+ local check_repo_command =
+ string.format("gh repo view %s/%s", vim.fn.system("gh api user --jq '.login'"):gsub("%s+", ""), repo_name)
+ local check_repo_result = vim.fn.systemlist(check_repo_command)
+ if not string.find(table.concat(check_repo_result), "Could not resolve to a Repository") then
+ print("Repository '" .. repo_name .. "' already exists on GitHub.")
+ return
+ end
+ -- Prompt for repository type
+ local repo_type = vim.fn.input("Enter the repository type (private/public): "):lower()
+ if repo_type ~= "private" and repo_type ~= "public" then
+ print("Invalid repository type. Please enter 'private' or 'public'.")
+ return
+ end
+ -- Set the repository type flag
+ local repo_type_flag = repo_type == "private" and "--private" or "--public"
+ -- Initialize the git repository and create the GitHub repository
+ local init_command = string.format("cd %s && git init", vim.fn.shellescape(cwd))
+ vim.fn.system(init_command)
+ local create_command =
+ string.format("cd %s && gh repo create %s %s --source=.", vim.fn.shellescape(cwd), repo_name, repo_type_flag)
+ local create_result = vim.fn.system(create_command)
+ -- Print the result of the repository creation command
+ if string.find(create_result, "https://github.com") then
+ print("Repository '" .. repo_name .. "' created successfully.")
+ else
+ print("Failed to create the repository: " .. create_result)
+ end
+end, { desc = "Create GitHub repository" })
+-- open repository
+local function open_github_link(use_root)
+ -- Get the full path of the current file
+ local file_path = vim.fn.expand("%:p")
+ if file_path == "" then
+ print("No file is currently open")
+ return
+ end
+
+ -- Get the Git repository root
+ local git_root = vim.fn.systemlist("git rev-parse --show-toplevel")[1]
+ if not git_root or git_root == "" then
+ print("Could not determine the root directory for the GitHub repository")
+ return
+ end
+
+ -- Get the origin URL of the repository
+ local origin_url = vim.fn.systemlist("git config --get remote.origin.url")[1]
+ if not origin_url or origin_url == "" then
+ print("Could not determine the origin URL for the GitHub repository")
+ return
+ end
+
+ -- Get the current branch name
+ local branch_name = vim.fn.systemlist("git rev-parse --abbrev-ref HEAD")[1]
+ if not branch_name or branch_name == "" then
+ print("Could not determine the current branch name")
+ return
+ end
+
+ -- Convert the origin URL to a GitHub HTTPS URL
+ local repo_url = origin_url:gsub("git@github.com:", "https://github.com/"):gsub("%.git$", "")
+
+ local github_url
+ if use_root then
+ -- Use the root repository URL
+ github_url = repo_url
+ else
+ -- Generate the link for the current file
+ local relative_path = file_path:sub(#git_root + 2) -- Extract the relative path
+ github_url = repo_url .. "/blob/" .. branch_name .. "/" .. relative_path
+ end
+
+ -- Open the URL in the default browser
+ local command = "xdg-open " .. vim.fn.shellescape(github_url)
+ vim.fn.system(command)
+
+ -- Print the opened URL
+ print("Opened GitHub link: " .. github_url)
+end
+
+-- Keybinding for opening the current file link
+vim.keymap.set("n", "<leader>go", function()
+ open_github_link(false) -- Use the current file link
+end, { desc = "Open GitHub link for the current file" })
+
+-- Keybinding for opening the repository root link
+vim.keymap.set("n", "<leader>gO", function()
+ open_github_link(true) -- Use the root repository URL
+end, { desc = "Open GitHub repository root" })
+
+-- paste a github link and add it in this format
+vim.keymap.set({ "n", "v", "i" }, "<M-;>", function()
+ -- Insert the text in the desired format
+ vim.cmd('normal! a[](){:target="_blank"} ')
+ vim.cmd("normal! F(pv2F/lyF[p")
+ -- Leave me in normal mode or command mode
+ vim.cmd("stopinsert")
+end, { desc = "Paste github link" })
+
+-- Health
+vim.keymap.set("n", "<leader>ch", ":checkhealth<cr>", { desc = "Check neovim health" })
+
+-- Highlights under cursor
+vim.keymap.set("n", "<leader>ip", vim.show_pos, { desc = "Inspect position" })
+
+-- Keywordprg
+vim.keymap.set("n", "<leader>K", "<cmd>norm! K<cr>", { desc = "Look up keyword" })
+
+-- Lines
+-- vim.keymap.set("n", "<A-,>", "<cmd>m .-2<cr>==", { desc = "Move line up" })
+-- vim.keymap.set("n", "<A-.>", "<cmd>m .+1<cr>==", { desc = "Move line down" })
+vim.keymap.set(
+ "i",
+ "<A-,>",
+ "<esc><cmd>m .-2<cr>==gi",
+ { noremap = true, silent = true, desc = "Move line up in insert mode" }
+)
+vim.keymap.set(
+ "i",
+ "<A-.>",
+ "<esc><cmd>m .+1<cr>==gi",
+ { noremap = true, silent = true, desc = "Move line down in insert mode" }
+)
+vim.keymap.set("v", "<C-,>", ":m '<-2<cr>gv=gv", { desc = "Move selected lines up" })
+vim.keymap.set("v", "<C-.>", ":m '>+1<cr>gv=gv", { desc = "Move selected lines down" })
+
+-- Markdown
+vim.keymap.set({ "n", "v" }, "gk", function()
+ -- `?` - Start a search backwards from the current cursor position.
+ -- `^` - Match the beginning of a line.
+ -- `##` - Match 2 ## symbols
+ -- `\\+` - Match one or more occurrences of prev element (#)
+ -- `\\s` - Match exactly one whitespace character following the hashes
+ -- `.*` - Match any characters (except newline) following the space
+ -- `$` - Match extends to end of line
+ vim.cmd("silent! ?^##\\+\\s.*$")
+ -- Clear the search highlight
+ vim.cmd("nohlsearch")
+end, { desc = "Go to previous markdown header" })
+vim.keymap.set({ "n", "v" }, "gj", function()
+ -- `/` - Start a search forwards from the current cursor position.
+ -- `^` - Match the beginning of a line.
+ -- `##` - Match 2 ## symbols
+ -- `\\+` - Match one or more occurrences of prev element (#)
+ -- `\\s` - Match exactly one whitespace character following the hashes
+ -- `.*` - Match any characters (except newline) following the space
+ -- `$` - Match extends to end of line
+ vim.cmd("silent! /^##\\+\\s.*$")
+ -- Clear the search highlight
+ vim.cmd("nohlsearch")
+end, { desc = "Go to next markdown header" })
+
+-- Marks
+vim.keymap.set("n", "<leader>mD", function()
+ -- Delete all marks in the current buffer
+ vim.cmd("delmarks!")
+ print("All marks deleted.")
+end, { desc = "Delete all marks" })
+
+-- Ownerships
+vim.keymap.set("n", "<leader>cx", "<cmd>!chmod +x %<cr>", { silent = true, desc = "Make file executable" })
+
+-- Remap Default
+vim.keymap.set("i", "jk", "<esc>", { noremap = true, silent = true, desc = "Escape to normal mode" })
+vim.keymap.set("i", "<C-c>", "<esc>", { noremap = true, silent = true, desc = "Escape to normal mode" })
+vim.keymap.set("i", "<C-a>", "<home>", { noremap = true, silent = true, desc = "Insert at beginning of line" })
+vim.keymap.set("i", "<C-e>", "<end>", { noremap = true, silent = true, desc = "Move to end of line" })
+vim.keymap.set("i", "<C-h>", "<left>", { noremap = true, silent = true, desc = "Move left" })
+vim.keymap.set("i", "<C-l>", "<right>", { noremap = true, silent = true, desc = "Move right" })
+vim.keymap.set("i", "<C-j>", "<down>", { noremap = true, silent = true, desc = "Move down" })
+vim.keymap.set("i", "<C-k>", "<up>", { noremap = true, silent = true, desc = "Move up" })
+vim.keymap.set("i", "<C-b>", "<up><end><cr>", { noremap = true, silent = true, desc = "New line above" })
+vim.keymap.set("i", "<C-f>", "<end><cr>", { noremap = true, silent = true, desc = "New line below" })
+vim.keymap.set("n", "<C-c>", ":", { noremap = true, desc = "Enter command mode" })
+vim.keymap.set("n", "J", "mzJ`z", { noremap = true, desc = "Join lines and keep cursor position" })
+vim.keymap.set("n", "n", "'Nn'[v:searchforward].'zv'", { expr = true, desc = "Next search result and center" })
+vim.keymap.set("x", "n", "'Nn'[v:searchforward]", { expr = true, desc = "Next search result in visual mode" })
+vim.keymap.set("o", "n", "'Nn'[v:searchforward]", { expr = true, desc = "Next search result in operator-pending mode" })
+vim.keymap.set("n", "N", "'nN'[v:searchforward].'zv'", { expr = true, desc = "Previous search result and center" })
+vim.keymap.set("x", "N", "'nN'[v:searchforward]", { expr = true, desc = "Previous search result in visual mode" })
+vim.keymap.set(
+ "o",
+ "N",
+ "'nN'[v:searchforward]",
+ { expr = true, desc = "Previous search result in operator-pending mode" }
+)
+vim.keymap.set("n", "x", '"_x', { desc = "Delete character without yanking" })
+local scroll_percentage = 0.50
+vim.keymap.set("n", "<C-d>", function()
+ local lines = math.floor(vim.api.nvim_win_get_height(0) * scroll_percentage)
+ vim.cmd("normal! " .. lines .. "jzz")
+end, { noremap = true, silent = true, desc = "Scroll down and center" })
+vim.keymap.set("n", "<C-u>", function()
+ local lines = math.floor(vim.api.nvim_win_get_height(0) * scroll_percentage)
+ vim.cmd("normal! " .. lines .. "kzz")
+end, { noremap = true, silent = true, desc = "Scroll up and center" })
+-- vim.keymap.set("n", "<C-d>", "<C-d>zz", { noremap = true, silent = true, desc = "Scroll down and center" })
+-- vim.keymap.set("n", "<C-u>", "<C-u>zz", { noremap = true, silent = true, desc = "Scroll up and center" })
+vim.keymap.set("n", "<C-b>", "<C-b>zz", { noremap = true, silent = true, desc = "Page up and center" })
+vim.keymap.set("n", "<C-f>", "<C-f>zz", { noremap = true, silent = true, desc = "Page down and center" })
+vim.keymap.set("n", "{", "{zz", { noremap = true, silent = true, desc = "Move to previous paragraph and center" })
+vim.keymap.set("n", "}", "}zz", { noremap = true, silent = true, desc = "Move to next paragraph and center" })
+vim.keymap.set("n", "G", "Gzz", { noremap = true, silent = true, desc = "Go to bottom of file and center" })
+vim.keymap.set("n", "gg", "ggzz", { noremap = true, silent = true, desc = "Go to top of file and center" })
+vim.keymap.set("n", "gd", "gdzz", { noremap = true, silent = true, desc = "Go to definition and center" })
+vim.keymap.set("n", "<C-i>", "<C-i>zz", { noremap = true, silent = true, desc = "Jump forward in jumplist and center" })
+vim.keymap.set(
+ "n",
+ "<C-o>",
+ "<C-o>zz",
+ { noremap = true, silent = true, desc = "Jump backward in jumplist and center" }
+)
+vim.keymap.set(
+ "n",
+ "%",
+ "%zz",
+ { noremap = true, silent = true, desc = "Jump to matching pair (e.g. brackets) and center" }
+)
+vim.keymap.set(
+ "n",
+ "*",
+ "*zz",
+ { noremap = true, silent = true, desc = "Search for next occurrence of word under cursor and center" }
+)
+vim.keymap.set(
+ "n",
+ "#",
+ "#zz",
+ { noremap = true, silent = true, desc = "Search for previous occurrence of word under cursor and center" }
+)
+
+vim.keymap.set(
+ { "n", "x" },
+ "j",
+ "v:count == 0 ? 'gj' : 'j'",
+ { expr = true, silent = true, desc = "Move down (visual line)" }
+)
+vim.keymap.set(
+ { "n", "x" },
+ "<down>",
+ "v:count == 0 ? 'gj' : 'j'",
+ { expr = true, silent = true, desc = "Move down (visual line)" }
+)
+vim.keymap.set(
+ { "n", "x" },
+ "k",
+ "v:count == 0 ? 'gk' : 'k'",
+ { expr = true, silent = true, desc = "Move up (visual line)" }
+)
+vim.keymap.set(
+ { "n", "x" },
+ "<up>",
+ "v:count == 0 ? 'gk' : 'k'",
+ { expr = true, silent = true, desc = "Move up (visual line)" }
+)
+vim.keymap.set("v", "<", "<gv", { desc = "Indent left and stay in visual mode" })
+vim.keymap.set("v", ">", ">gv", { desc = "Indent right and stay in visual mode" })
+vim.keymap.set("v", "J", ":m '>+1<cr>gv=gv", { desc = "Move line down in visual mode" })
+vim.keymap.set("v", "K", ":m '<-2<cr>gv=gv", { desc = "Move line up in visual mode" })
+vim.keymap.set("n", "sl", "vg_", { desc = "Select to end of line" })
+vim.keymap.set("n", "sp", "ggVGp", { desc = "Select all and paste" })
+vim.keymap.set("n", "sv", "ggVG", { desc = "Select all" })
+vim.keymap.set("n", "gp", "`[v`]", { desc = "Select pasted text" })
+vim.keymap.set("n", "ss", ":s/\\v", { desc = "Search and replace on line" })
+vim.keymap.set("n", "SS", ":%s/\\v", { desc = "Search and replace in file" })
+vim.keymap.set("v", "<leader><C-s>", ":s/\\%V", { desc = "Search only in visual selection using %V atom" })
+vim.keymap.set("v", "<C-r>", '"hy:%s/\\v<C-r>h//g<left><left>', { desc = "Change selection" })
+
+-- Remove
+local function delete_current_file()
+ local filepath = vim.fn.expand("%:p")
+ local filename = vim.fn.expand("%:t") -- Get the current filename
+ if filepath and filepath ~= "" then
+ -- Check if trash utility is installed
+ if vim.fn.executable("trash") == 0 then
+ vim.api.nvim_echo({
+ { "- Trash utility not installed. Make sure to install it first\n", "ErrorMsg" },
+ { "- Install `trash-cli`\n", nil },
+ }, false, {})
+ return
+ end
+ -- Prompt for confirmation before deleting the file
+ vim.ui.input({
+ prompt = 'Do you want to delete "' .. filename .. '"? (y/n): ',
+ }, function(input)
+ if input == nil then
+ return
+ end
+
+ if input:lower() == "y" or input:lower() == "yes" then
+ -- Delete the file using trash app
+ local success, _ = pcall(function()
+ vim.fn.system({ "trash", vim.fn.fnameescape(filepath) })
+ end)
+ if success then
+ vim.api.nvim_echo({
+ { "File deleted from disk:\n", "Normal" },
+ { filepath, "Normal" },
+ }, false, {})
+ -- Close the buffer after deleting the file
+ vim.cmd("bd!")
+ else
+ vim.api.nvim_echo({
+ { "Failed to delete file:\n", "ErrorMsg" },
+ { filepath, "ErrorMsg" },
+ }, false, {})
+ end
+ else
+ vim.api.nvim_echo({
+ { "File deletion canceled.", "Normal" },
+ }, false, {})
+ end
+ end)
+ else
+ vim.api.nvim_echo({
+ { "No file to delete", "WarningMsg" },
+ }, false, {})
+ end
+end
+
+vim.keymap.set("n", "<leader>rm", function()
+ delete_current_file()
+end, { desc = "Remove current file" })
+
+-- Scripts
+vim.api.nvim_set_keymap(
+ "n",
+ "<leader>rr",
+ ':w!<cr>:lua vim.cmd("split | resize 10 | terminal compiler " .. vim.fn.expand("%:p"))<cr>',
+ { noremap = true, silent = true, desc = "Run compiler interactively" }
+)
+vim.api.nvim_set_keymap(
+ "n",
+ "<leader>ov",
+ ":!opout <C-r>%<cr><cr>",
+ { noremap = true, silent = true, desc = "Docs viewer" }
+)
+
+-- Source
+-- source nvim config
+vim.keymap.set("n", "<leader>SO", function()
+ vim.cmd("so")
+end, { desc = "Source current file" })
+-- reload zsh configuration by sourcing ~/.config/zsh/.zshrc in a separate shell
+vim.keymap.set("n", "<leader>SZ", function()
+ -- Define the command to source zshrc
+ local command = "source ~/.config/zsh/.zshrc"
+ -- Execute the command in a new Zsh shell
+ local full_command = "zsh -c '" .. command .. "'"
+ -- Run the command and capture the output
+ local output = vim.fn.system(full_command)
+ -- Check the exit status of the command
+ local exit_code = vim.v.shell_error
+ if exit_code == 0 then
+ vim.api.nvim_echo({ { "Successfully sourced ~/.config/zsh/.zshrc", "NormalMsg" } }, false, {})
+ else
+ vim.api.nvim_echo({
+ { "Failed to source ~/.config/zsh/.zshrc:", "ErrorMsg" },
+ { output, "ErrorMsg" },
+ }, false, {})
+ end
+end, { desc = "Source zshrc" })
+-- source shortcuts from bm-files and bm-folders
+local shortcuts_file = vim.fn.expand("~/.config/nvim/shortcuts.lua")
+local file = io.open(shortcuts_file, "r")
+if file then
+ file:close()
+ vim.cmd("silent! source " .. shortcuts_file)
+end
+
+-- Spell
+vim.keymap.set("n", "zp", function()
+ vim.opt.spelllang = { "en", "cjk" }
+ vim.cmd("echo 'Spell language set to English and CJK'")
+end, { desc = "Spelling language English and CJK" })
+-- repeat the replacement done by |z=| for all matches with the replaced word in the current window.
+vim.keymap.set("n", "z.", function()
+ vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(":spellr\n", true, false, true), "m", true)
+end, { desc = "Spelling repeat" })
+
+-- Sudo
+vim.keymap.set("n", "<leader>SW", "<cmd>SudoWrite<cr><cr>", { silent = true, desc = "Save file with sudo" })
+vim.keymap.set("n", "<leader>SWQ", "<cmd>SudoWritequit<cr>", { silent = true, desc = "Save and quit with sudo" })
+
+-- Surround
+vim.keymap.set("n", "sau", function()
+ local line = vim.api.nvim_get_current_line()
+ local col = vim.api.nvim_win_get_cursor(0)[2] + 1 -- Adjust for 0-index in Lua
+ -- This makes the `s` optional so it matches both http and https
+ local pattern = "https?://[^ ,;'\"<>%s)]*"
+ -- Find the starting and ending positions of the URL
+ local s, e = string.find(line, pattern)
+ while s and e do
+ if s <= col and e >= col then
+ -- When the cursor is within the URL
+ local url = string.sub(line, s, e)
+ -- Update the line with backticks around the URL
+ local new_line = string.sub(line, 1, s - 1) .. "`" .. url .. "`" .. string.sub(line, e + 1)
+ vim.api.nvim_set_current_line(new_line)
+ vim.cmd("silent write")
+ return
+ end
+ -- Find the next URL in the line
+ s, e = string.find(line, pattern, e + 1)
+ -- Save the file to update trouble list
+ end
+ print("No URL found under cursor")
+end, { desc = "Add surrounding to URL" })
+vim.keymap.set("v", "<leader>bl", function()
+ -- Get the selected text range
+ local start_row, start_col = unpack(vim.fn.getpos("'<"), 2, 3)
+ local end_row, end_col = unpack(vim.fn.getpos("'>"), 2, 3)
+ -- Get the selected lines
+ local lines = vim.api.nvim_buf_get_lines(0, start_row - 1, end_row, false)
+ local selected_text = table.concat(lines, "\n"):sub(start_col, #lines == 1 and end_col or -1)
+ if selected_text:match("^%*%*.*%*%*$") then
+ vim.notify("Text already bold", vim.log.levels.INFO)
+ else
+ vim.cmd("normal 2gsa*")
+ end
+end, { desc = "Bold current selection" })
+vim.keymap.set("n", "gbd", function()
+ local cursor_pos = vim.api.nvim_win_get_cursor(0)
+ local current_buffer = vim.api.nvim_get_current_buf()
+ local start_row = cursor_pos[1] - 1
+ local col = cursor_pos[2]
+ -- Get the current line
+ local line = vim.api.nvim_buf_get_lines(current_buffer, start_row, start_row + 1, false)[1]
+ -- Check if the cursor is on an asterisk
+ if line:sub(col + 1, col + 1):match("%*") then
+ vim.notify("Cursor is on an asterisk, run inside the bold text", vim.log.levels.WARN)
+ return
+ end
+ -- Search for '**' to the left of the cursor position
+ local left_text = line:sub(1, col)
+ local bold_start = left_text:reverse():find("%*%*")
+ if bold_start then
+ bold_start = col - bold_start
+ end
+ -- Search for '**' to the right of the cursor position and in following lines
+ local right_text = line:sub(col + 1)
+ local bold_end = right_text:find("%*%*")
+ local end_row = start_row
+ while not bold_end and end_row < vim.api.nvim_buf_line_count(current_buffer) - 1 do
+ end_row = end_row + 1
+ local next_line = vim.api.nvim_buf_get_lines(current_buffer, end_row, end_row + 1, false)[1]
+ if next_line == "" then
+ break
+ end
+ right_text = right_text .. "\n" .. next_line
+ bold_end = right_text:find("%*%*")
+ end
+ if bold_end then
+ bold_end = col + bold_end
+ end
+ -- Remove '**' markers if found, otherwise bold the word
+ if bold_start and bold_end then
+ -- Extract lines
+ local text_lines = vim.api.nvim_buf_get_lines(current_buffer, start_row, end_row + 1, false)
+ local text = table.concat(text_lines, "\n")
+ -- Calculate positions to correctly remove '**'
+ -- vim.notify("bold_start: " .. bold_start .. ", bold_end: " .. bold_end)
+ local new_text = text:sub(1, bold_start - 1) .. text:sub(bold_start + 2, bold_end - 1) .. text:sub(bold_end + 2)
+ local new_lines = vim.split(new_text, "\n")
+ -- Set new lines in buffer
+ vim.api.nvim_buf_set_lines(current_buffer, start_row, end_row + 1, false, new_lines)
+ -- vim.notify("Unbolded text", vim.log.levels.INFO)
+ else
+ -- Bold the word at the cursor position if no bold markers are found
+ local before = line:sub(1, col)
+ local after = line:sub(col + 1)
+ local inside_surround = before:match("%*%*[^%*]*$") and after:match("^[^%*]*%*%*")
+ if inside_surround then
+ vim.cmd("normal gsd*.")
+ else
+ vim.cmd("normal viw")
+ vim.cmd("normal 2gsa*")
+ end
+ vim.notify("Bolded current word", vim.log.levels.INFO)
+ end
+end, { desc = "Toggle bold" })
+
+-- Tab
+vim.keymap.set("n", "<leader><tab>l", "<cmd>tablast<cr>", { desc = "Last Tab" })
+vim.keymap.set("n", "]]<tab>", "<cmd>tablast<cr>", { desc = "Last Tab" })
+vim.keymap.set("n", "<leader><tab>o", "<cmd>tabonly<cr>", { desc = "Close Other Tabs" })
+vim.keymap.set("n", "<leader><tab>f", "<cmd>tabfirst<cr>", { desc = "First Tab" })
+vim.keymap.set("n", "[[<tab>", "<cmd>tabfirst<cr>", { desc = "First Tab" })
+vim.keymap.set("n", "<leader><tab><tab>", "<cmd>tabnew<cr>", { desc = "New Tab" })
+vim.keymap.set("n", "<leader><tab>n", "<cmd>tabnext<cr>", { desc = "Next Tab" })
+vim.keymap.set("n", "]<tab>", "<cmd>tabnext<cr>", { desc = "Next Tab" })
+vim.keymap.set("n", "<leader><tab>d", "<cmd>tabclose<cr>", { desc = "Close Tab" })
+vim.keymap.set("n", "<leader><tab>p", "<cmd>tabprevious<cr>", { desc = "Previous Tab" })
+vim.keymap.set("n", "[<tab>", "<cmd>tabprevious<cr>", { desc = "Previous Tab" })
+
+-- Terminal
+vim.keymap.set("n", "<leader>te", "<cmd>term<cr>", { desc = "Open terminal" })
+vim.keymap.set("n", "<leader>t-", "<cmd>sp term://zsh | startinsert<cr>", { desc = "Split terminal (horizontal)" })
+vim.keymap.set("n", "<leader>t|", "<cmd>vsp term://zsh | startinsert<cr>", { desc = "Split terminal (vertical)" })
+vim.keymap.set("t", "<esc><esc>", "<C-\\><C-n>", { desc = "Escape terminal mode" })
+vim.keymap.set("t", "<C-h>", "<cmd>wincmd h<cr>", { desc = "Move to left window" })
+vim.keymap.set("t", "<C-j>", "<cmd>wincmd j<cr>", { desc = "Move to window below" })
+vim.keymap.set("t", "<C-k>", "<cmd>wincmd k<cr>", { desc = "Move to window above" })
+vim.keymap.set("t", "<C-l>", "<cmd>wincmd l<cr>", { desc = "Move to right window" })
+vim.keymap.set("t", "<C-Space>l", "clear<cr>", { silent = true, desc = "Clear terminal" })
+vim.keymap.set("t", "<C-\\>", "<C-\\><C-n>iexit<cr>", { desc = "Close terminal" })
+vim.keymap.set("t", "<C-/>", "<cmd>close<cr>", { desc = "Close terminal" })
+vim.keymap.set("t", "<C-_>", "<cmd>close<cr>", { desc = "Close terminal" })
+
+-- Tmux
+if vim.env.TMUX then
+ vim.keymap.set(
+ "n",
+ "<leader>tm",
+ "<cmd>silent !~/.config/tmux/plugins/tmux-fzf/scripts/session.sh<cr>",
+ { desc = "Find tmux session" }
+ )
+ vim.keymap.set("n", "<leader>RS", function()
+ vim.fn.system("restartnvim")
+ end, { noremap = true, silent = true, desc = "Restart nvim (tmux)" })
+end
+
+-- Todo
+-- detect todos and toggle between ":" and ";", or show a message if not found
+-- this is to "mark them as done"
+vim.keymap.set("n", "<leader>td", function()
+ -- Get the current line
+ local current_line = vim.fn.getline(".")
+ -- Get the current line number
+ local line_number = vim.fn.line(".")
+ if string.find(current_line, "TODO:") then
+ -- Replace the first occurrence of ":" with ";"
+ local new_line = current_line:gsub("TODO:", "TODO;")
+ -- Set the modified line
+ vim.fn.setline(line_number, new_line)
+ elseif string.find(current_line, "TODO;") then
+ -- Replace the first occurrence of ";" with ":"
+ local new_line = current_line:gsub("TODO;", "TODO:")
+ -- Set the modified line
+ vim.fn.setline(line_number, new_line)
+ else
+ vim.cmd("echo 'todo item not detected'")
+ end
+end, { desc = "Toggle TODO item done or not" })
+
+-- Windows
+vim.keymap.set("n", "<C-h>", "<C-w><C-h>", { desc = "Move to left window" })
+vim.keymap.set("n", "<C-j>", "<C-w><C-j>", { desc = "Move to window below" })
+vim.keymap.set("n", "<C-k>", "<C-w><C-k>", { desc = "Move to window above" })
+vim.keymap.set("n", "<C-l>", "<C-w><C-l>", { desc = "Move to right window" })
+-- vim.keymap.set("n", "<C-up>", "<cmd>resize +2<cr>", { desc = "Increase window height" })
+-- vim.keymap.set("n", "<C-down>", "<cmd>resize -2<cr>", { desc = "Decrease window height" })
+-- vim.keymap.set("n", "<C-left>", "<cmd>vertical resize -2<cr>", { desc = "Decrease window width" })
+-- vim.keymap.set("n", "<C-right>", "<cmd>vertical resize +2<cr>", { desc = "Increase window width" })
+
+function WordDefinition(input)
+ -- Function to run the dict command and return its output
+ local function get_output(word)
+ local escaped_word = vim.fn.shellescape(word)
+ return vim.fn.system("dict " .. escaped_word)
+ end
+
+ -- Function to process the word for singular/plural handling
+ local function get_definition(word)
+ -- Attempt to derive the singular form
+ local singular = word
+ if word:sub(-2) == "es" then
+ singular = word:sub(1, -3) -- Remove 'es'
+ elseif word:sub(-1) == "s" then
+ singular = word:sub(1, -2) -- Remove 's'
+ end
+
+ -- Fetch output for both singular and original word
+ local singular_output = get_output(singular)
+ local original_output = get_output(word)
+
+ -- Determine which output to prioritize
+ if singular ~= word and not vim.startswith(singular_output, "No definitions found") then
+ return singular_output -- Use singular if valid and different
+ else
+ return original_output -- Otherwise, use the original word
+ end
+ end
+
+ -- Get the definition and output for the word
+ local output = get_definition(input)
+
+ -- Create a new buffer and display the result
+ local bufnr = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_set_current_buf(bufnr)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.split(output, "\n"))
+end
+vim.api.nvim_set_keymap(
+ "n",
+ "<leader>k",
+ ":lua WordDefinition(vim.fn.expand('<cword>'))<cr>",
+ { noremap = true, silent = true, desc = "Word definition" }
+)
+
+-- Lazy
+vim.keymap.set("n", "<leader>L", "<cmd>Lazy<cr>", { desc = "Open lazy plugin manager" })
+
+-- Mason
+vim.keymap.set("n", "<leader>M", "<cmd>Mason<cr>", { desc = "Open mason" })
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/core/lazy.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/lazy.lua
new file mode 100644
index 0000000..8097325
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/lazy.lua
@@ -0,0 +1,17 @@
+local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
+if not vim.loop.fs_stat(lazypath) then
+ local lazyrepo = "https://github.com/folke/lazy.nvim.git"
+ vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
+end ---@diagnostic disable-next-line: undefined-field
+vim.opt.rtp:prepend(lazypath)
+
+require("lazy").setup({
+ spec = {
+ { import = "thesiahxyz.plugins" },
+ },
+ install = {
+ colorscheme = { "catppuccin" },
+ },
+ change_detection = { notify = false },
+ checker = { enabled = false },
+})
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/core/options.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/options.lua
new file mode 100644
index 0000000..9c1a6a6
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/core/options.lua
@@ -0,0 +1,42 @@
+vim.g.have_nerd_font = true
+-- vim.g.netrw_browse_split = 0
+-- vim.g.netrw_banner = 1
+-- vim.g.netrw_browsex_viewer = "xdg-open"
+-- vim.g.netrw_liststyle = 0
+-- vim.g.netrw_list_hide = "^.git"
+-- vim.g.netrw_winsize = 25
+-- vim.g.loaded_netrw = 1
+-- vim.g.loaded_netrwPlugin = 1
+vim.opt.backup = false
+vim.opt.breakindent = true
+vim.opt.conceallevel = 1
+vim.opt.cursorline = true
+vim.opt.expandtab = true
+vim.opt.hlsearch = true
+vim.opt.ignorecase = true
+vim.opt.inccommand = "split"
+vim.opt.incsearch = true
+vim.opt.isfname:append("@-@")
+vim.opt.list = true
+vim.opt.listchars = { tab = " ", trail = " ", nbsp = " " }
+vim.opt.mouse = "a"
+vim.opt.number = true
+vim.opt.relativenumber = true
+vim.opt.scrolloff = 8
+vim.opt.shiftwidth = 2
+vim.opt.showmode = false
+vim.opt.signcolumn = "yes"
+vim.opt.smartcase = true
+vim.opt.smartindent = true
+vim.opt.softtabstop = 2
+vim.opt.spellfile = vim.fn.expand(vim.fn.stdpath("config") .. "/lua/thesiahxyz/spells/en.utf-8.add")
+vim.opt.splitbelow = true
+vim.opt.splitright = true
+vim.opt.swapfile = false
+vim.opt.tabstop = 2
+vim.opt.termguicolors = true
+vim.opt.timeoutlen = 300
+vim.opt.updatetime = 300
+vim.opt.undofile = true
+vim.opt.undodir = os.getenv("HOME") .. "/.local/share/history/vim_history"
+vim.opt.wrap = false
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/health.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/health.lua
new file mode 100644
index 0000000..dba5f3a
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/health.lua
@@ -0,0 +1,45 @@
+local check_version = function()
+ local verstr = string.format("%s.%s.%s", vim.version().major, vim.version().minor, vim.version().patch)
+ if not vim.version.cmp then
+ vim.health.error(string.format("Neovim out of date: '%s'. Upgrade to latest stable or nightly", verstr))
+ return
+ end
+
+ if vim.version.cmp(vim.version(), { 0, 9, 4 }) >= 0 then
+ vim.health.ok(string.format("Neovim version is: '%s'", verstr))
+ else
+ vim.health.error(string.format("Neovim out of date: '%s'. Upgrade to latest stable or nightly", verstr))
+ end
+end
+
+local check_external_reqs = function()
+ -- Basic utils: `git`, `make`, `unzip`
+ for _, exe in ipairs({ "git", "make", "unzip", "rg" }) do
+ local is_executable = vim.fn.executable(exe) == 1
+ if is_executable then
+ vim.health.ok(string.format("Found executable: '%s'", exe))
+ else
+ vim.health.warn(string.format("Could not find executable: '%s'", exe))
+ end
+ end
+
+ return true
+end
+
+return {
+ check = function()
+ vim.health.start("TheSiahxyz")
+
+ vim.health.info([[NOTE: Not every warning is a 'must-fix' in `:checkhealth`
+
+ Fix only warnings for plugins and languages you intend to use.
+ Mason will give warnings for languages that are not installed.
+ You do not need to install, unless you want to use those languages!]])
+
+ local uv = vim.uv or vim.loop
+ vim.health.info("System Information: " .. vim.inspect(uv.os_uname()))
+
+ check_version()
+ check_external_reqs()
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/init.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/init.lua
new file mode 100644
index 0000000..2a8bc78
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/init.lua
@@ -0,0 +1,15 @@
+-- Core
+require("thesiahxyz.core.autocmds")
+require("thesiahxyz.core.keymaps")
+require("thesiahxyz.core.options")
+require("thesiahxyz.core.lazy")
+
+-- Custom
+for _, file in ipairs(vim.fn.readdir(vim.fn.stdpath("config") .. "/lua/thesiahxyz/utils", [[v:val =~ '\.lua$']])) do
+ require("thesiahxyz.utils." .. file:gsub("%.lua$", ""))
+end
+
+-- Plenary
+function R(name)
+ require("plenary.reload").reload_module(name)
+end
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ai.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ai.lua
new file mode 100644
index 0000000..2fca9f7
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ai.lua
@@ -0,0 +1,286 @@
+return {
+ {
+ "robitx/gp.nvim",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>G", group = "GPT" },
+ { "<leader>Gg", group = "Gp" },
+ { "<leader>GW", group = "Whisper" },
+ })
+ end,
+ config = function()
+ local function keymapOptions(desc)
+ return {
+ noremap = true,
+ silent = true,
+ nowait = true,
+ desc = desc,
+ }
+ end
+
+ local conf = {
+ -- For customization, refer to Install > Configuration in the Documentation/Readme
+ -- openai_api_key = { "pass", "show", "api/chatGPT/nvim" },
+ openai_api_key = { "pass", "show", "api/chatGPT/nvim" },
+ providers = {
+ openai = {
+ disable = false,
+ endpoint = "https://api.openai.com/v1/chat/completions",
+ -- secret = { "pass", "show", "api/chatGPT/nvim" },
+ },
+ },
+ hooks = {
+ -- GpInspectPlugin provides a detailed inspection of the plugin state
+ InspectPlugin = function(plugin, params)
+ local bufnr = vim.api.nvim_create_buf(false, true)
+ local copy = vim.deepcopy(plugin)
+ local key = copy.config.openai_api_key or ""
+ copy.config.openai_api_key = key:sub(1, 3) .. string.rep("*", #key - 6) .. key:sub(-3)
+ local plugin_info = string.format("Plugin structure:\n%s", vim.inspect(copy))
+ local params_info = string.format("Command params:\n%s", vim.inspect(params))
+ local lines = vim.split(plugin_info .. "\n" .. params_info, "\n")
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
+ vim.api.nvim_win_set_buf(0, bufnr)
+ end,
+
+ -- GpInspectLog for checking the log file
+ InspectLog = function(plugin, params)
+ local log_file = plugin.config.log_file
+ local buffer = plugin.helpers.get_buffer(log_file)
+ if not buffer then
+ vim.cmd("e " .. log_file)
+ else
+ vim.cmd("buffer " .. buffer)
+ end
+ end,
+
+ -- GpImplement rewrites the provided selection/range based on comments in it
+ Implement = function(gp, params)
+ local template = "Having following from {{filename}}:\n\n"
+ .. "```{{filetype}}\n{{selection}}\n```\n\n"
+ .. "Please rewrite this according to the contained instructions."
+ .. "\n\nRespond exclusively with the snippet that should replace the selection above."
+
+ local agent = gp.get_command_agent()
+ gp.logger.info("Implementing selection with agent: " .. agent.name)
+
+ gp.Prompt(
+ params,
+ gp.Target.rewrite,
+ agent,
+ template,
+ nil, -- command will run directly without any prompting for user input
+ nil -- no predefined instructions (e.g. speech-to-text from Whisper)
+ )
+ end,
+
+ -- your own functions can go here, see README for more examples like
+ -- :GpExplain, :GpUnitTests.., :GpTranslator etc.
+
+ -- example of making :%GpChatNew a dedicated command which
+ -- opens new chat with the entire current buffer as a context
+ BufferChatNew = function(gp, _)
+ -- call GpChatNew command in range mode on whole buffer
+ vim.api.nvim_command("%" .. gp.config.cmd_prefix .. "ChatNew")
+ end,
+
+ -- example of adding command which opens new chat dedicated for translation
+ Translator = function(gp, params)
+ local chat_system_prompt = "You are a Translator, please translate between English and Korean."
+ gp.cmd.ChatNew(params, chat_system_prompt)
+
+ -- -- you can also create a chat with a specific fixed agent like this:
+ -- local agent = gp.get_chat_agent("ChatGPT4o")
+ -- gp.cmd.ChatNew(params, chat_system_prompt, agent)
+ end,
+
+ -- example of adding command which writes unit tests for the selected code
+ UnitTests = function(gp, params)
+ local template = "I have the following code from {{filename}}:\n\n"
+ .. "```{{filetype}}\n{{selection}}\n```\n\n"
+ .. "Please respond by writing table driven unit tests for the code above."
+ local agent = gp.get_command_agent()
+ gp.Prompt(params, gp.Target.enew, agent, template)
+ end,
+
+ -- example of adding command which explains the selected code
+ Explain = function(gp, params)
+ local template = "I have the following code from {{filename}}:\n\n"
+ .. "```{{filetype}}\n{{selection}}\n```\n\n"
+ .. "Please respond by explaining the code above."
+ local agent = gp.get_chat_agent()
+ gp.Prompt(params, gp.Target.popup, agent, template)
+ end,
+
+ -- example of usig enew as a function specifying type for the new buffer
+ CodeReview = function(gp, params)
+ local template = "I have the following code from {{filename}}:\n\n"
+ .. "```{{filetype}}\n{{selection}}\n```\n\n"
+ .. "Please analyze for code smells and suggest improvements."
+ local agent = gp.get_chat_agent()
+ gp.Prompt(params, gp.Target.enew("markdown"), agent, template)
+ end,
+ },
+ }
+ require("gp").setup(conf)
+
+ -- Setup shortcuts here (see Usage > Shortcuts in the Documentation/Readme)
+ vim.keymap.set({ "n", "i" }, "<leader>Gc", "<cmd>GpChatNew<cr>", keymapOptions("New chat"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gb", "<cmd>GpBufferChatNew<cr>", keymapOptions("New buffer chat"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gt", "<cmd>GpChatToggle<cr>", keymapOptions("Toggle chat"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gf", "<cmd>GpChatFinder<cr>", keymapOptions("Chat finder"))
+
+ vim.keymap.set("v", "<leader>Gc", ":<C-u>'<,'>GpChatNew<cr>", keymapOptions("Chat new"))
+ vim.keymap.set("v", "<leader>Gb", ":<C-u>'<,'>GpBufferChatNew<cr>", keymapOptions("Buffer chat new"))
+ vim.keymap.set("v", "<leader>Gp", ":<C-u>'<,'>GpChatPaste<cr>", keymapOptions("Chat paste"))
+ vim.keymap.set("v", "<leader>Gt", ":<C-u>'<,'>GpChatToggle<cr>", keymapOptions("Toggle chat"))
+
+ vim.keymap.set({ "n", "i" }, "<leader>Gh", "<cmd>gpchatnew split<cr>", keymapOptions("New chat split"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gv", "<cmd>gpchatnew vsplit<cr>", keymapOptions("New chat vsplit"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gn", "<cmd>gpchatnew tabnew<cr>", keymapOptions("New chat tabnew"))
+
+ vim.keymap.set("v", "<leader>Gh", ":<C-u>'<,'>GpChatNew split<cr>", keymapOptions("Chat new split"))
+ vim.keymap.set("v", "<leader>Gv", ":<C-u>'<,'>GpChatNew vsplit<cr>", keymapOptions("Chat new vsplit"))
+ vim.keymap.set("v", "<leader>Gn", ":<C-u>'<,'>GpChatNew tabnew<cr>", keymapOptions("Chat new tabnew"))
+
+ -- Prompt commands
+ vim.keymap.set({ "n", "i" }, "<leader>Gw", "<cmd>GpRewrite<cr>", keymapOptions("Inline rewrite"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gr", "<cmd>GpCodeReview<cr>", keymapOptions("Code review"))
+ vim.keymap.set({ "n", "i" }, "<leader>G]", "<cmd>GpAppend<cr>", keymapOptions("Append (after)"))
+ vim.keymap.set({ "n", "i" }, "<leader>G[", "<cmd>GpPrepend<cr>", keymapOptions("Prepend (before)"))
+
+ vim.keymap.set("v", "<leader>Gw", ":<C-u>'<,'>GpRewrite<cr>", keymapOptions("Rewrite"))
+ vim.keymap.set("v", "<leader>Gr", ":<C-u>'<,'>GpCodeReview<cr>", keymapOptions("Code review"))
+ vim.keymap.set("v", "<leader>G]", ":<C-u>'<,'>GpAppend<cr>", keymapOptions("Append (after)"))
+ vim.keymap.set("v", "<leader>G[", ":<C-u>'<,'>GpPrepend<cr>", keymapOptions("Prepend (before)"))
+ vim.keymap.set("v", "<leader>Gi", ":<C-u>'<,'>GpImplement<cr>", keymapOptions("Implement selection"))
+
+ vim.keymap.set({ "n", "i" }, "<leader>Ggp", "<cmd>GpPopup<cr>", keymapOptions("Popup"))
+ vim.keymap.set({ "n", "i" }, "<leader>Gge", "<cmd>GpEnew<cr>", keymapOptions("GpEnew"))
+ vim.keymap.set({ "n", "i" }, "<leader>Ggc", "<cmd>GpNew<cr>", keymapOptions("GpNew"))
+ vim.keymap.set({ "n", "i" }, "<leader>Ggv", "<cmd>GpVnew<cr>", keymapOptions("GpVnew"))
+ vim.keymap.set({ "n", "i" }, "<leader>Ggn", "<cmd>GpTabnew<cr>", keymapOptions("GpTabnew"))
+
+ vim.keymap.set("v", "<leader>Ggp", ":<C-u>'<,'>GpPopup<cr>", keymapOptions("Popup"))
+ vim.keymap.set("v", "<leader>Gge", ":<C-u>'<,'>GpEnew<cr>", keymapOptions("GpEnew"))
+ vim.keymap.set("v", "<leader>Ggc", ":<C-u>'<,'>GpNew<cr>", keymapOptions("GpNew"))
+ vim.keymap.set("v", "<leader>Ggv", ":<C-u>'<,'>GpVnew<cr>", keymapOptions("GpVnew"))
+ vim.keymap.set("v", "<leader>Ggn", ":<C-u>'<,'>GpTabnew<cr>", keymapOptions("GpTabnew"))
+
+ vim.keymap.set({ "n", "i" }, "<leader>Gx", "<cmd>GpContext<cr>", keymapOptions("Toggle context"))
+ vim.keymap.set("v", "<leader>Gx", ":<C-u>'<,'>GpContext<cr>", keymapOptions("Toggle context"))
+
+ vim.keymap.set({ "n", "i", "v", "x" }, "<leader>Ggs", "<cmd>GpStop<cr>", keymapOptions("Stop"))
+ vim.keymap.set({ "n", "i", "v", "x" }, "<leader>Gg]", "<cmd>GpNextAgent<cr>", keymapOptions("Next agent"))
+
+ -- optional Whisper commands with prefix <C-g>w
+ vim.keymap.set({ "n", "i" }, "<leader>GWw", "<cmd>GpWhisper<cr>", keymapOptions("Whisper"))
+ vim.keymap.set("v", "<leader>GWw", ":<C-u>'<,'>GpWhisper<cr>", keymapOptions("Whisper"))
+
+ vim.keymap.set({ "n", "i" }, "<leader>GWr", "<cmd>GpWhisperRewrite<cr>", keymapOptions("Inline rewrite"))
+ vim.keymap.set({ "n", "i" }, "<leader>GW]", "<cmd>GpWhisperAppend<cr>", keymapOptions("Append (after)"))
+ vim.keymap.set({ "n", "i" }, "<leader>GW[", "<cmd>GpWhisperPrepend<cr>", keymapOptions("Prepend (before) "))
+
+ vim.keymap.set("v", "<leader>GWr", ":<C-u>'<,'>GpWhisperRewrite<cr>", keymapOptions("Rewrite"))
+ vim.keymap.set("v", "<leader>GW]", ":<C-u>'<,'>GpWhisperAppend<cr>", keymapOptions("Append (after)"))
+ vim.keymap.set("v", "<leader>GW[", ":<C-u>'<,'>GpWhisperPrepend<cr>", keymapOptions("Prepend (before)"))
+
+ vim.keymap.set({ "n", "i" }, "<leader>GWp", "<cmd>GpWhisperPopup<cr>", keymapOptions("Popup"))
+ vim.keymap.set({ "n", "i" }, "<leader>GWe", "<cmd>GpWhisperEnew<cr>", keymapOptions("Enew"))
+ vim.keymap.set({ "n", "i" }, "<leader>GWc", "<cmd>GpWhisperNew<cr>", keymapOptions("New"))
+ vim.keymap.set({ "n", "i" }, "<leader>GWv", "<cmd>GpWhisperVnew<cr>", keymapOptions("Vnew"))
+ vim.keymap.set({ "n", "i" }, "<leader>GWn", "<cmd>GpWhisperTabnew<cr>", keymapOptions("Tabnew"))
+
+ vim.keymap.set("v", "<leader>GWp", ":<C-u>'<,'>GpWhisperPopup<cr>", keymapOptions("Popup"))
+ vim.keymap.set("v", "<leader>GWe", ":<C-u>'<,'>GpWhisperEnew<cr>", keymapOptions("Enew"))
+ vim.keymap.set("v", "<leader>GWc", ":<C-u>'<,'>GpWhisperNew<cr>", keymapOptions("New"))
+ vim.keymap.set("v", "<leader>GWv", ":<C-u>'<,'>GpWhisperVnew<cr>", keymapOptions("Vnew"))
+ vim.keymap.set("v", "<leader>GWn", ":<C-u>'<,'>GpWhisperTabnew<cr>", keymapOptions("Tabnew"))
+ end,
+ },
+ {
+ "zbirenbaum/copilot.lua",
+ cmd = "Copilot",
+ build = ":Copilot auth",
+ event = "InsertEnter",
+ dependencies = {
+ "hrsh7th/nvim-cmp",
+ { "AndreM222/copilot-lualine" },
+ {
+ "zbirenbaum/copilot-cmp",
+ config = function()
+ require("copilot_cmp").setup()
+ end,
+ },
+ },
+ config = function()
+ require("copilot").setup({
+ panel = {
+ enabled = true,
+ auto_refresh = true,
+ keymap = {
+ jump_prev = "[a",
+ jump_next = "]a",
+ accept = "<CR>",
+ refresh = "gr",
+ open = "<C-CR>",
+ },
+ layout = {
+ position = "right", -- | top | left | right
+ ratio = 0.4,
+ },
+ },
+ suggestion = {
+ enabled = true,
+ auto_trigger = true,
+ hide_during_completion = true,
+ debounce = 75,
+ keymap = {
+ accept = "<C-q>",
+ accept_word = false,
+ accept_line = false,
+ next = "<C-n>",
+ prev = "<C-p>",
+ dismiss = "<C-\\>",
+ },
+ },
+ filetypes = {
+ cvs = false,
+ gitcommit = false,
+ gitrebase = false,
+ help = true,
+ hgcommit = false,
+ markdown = true,
+ sh = function()
+ if string.match(vim.fs.basename(vim.api.nvim_buf_get_name(0)), "^%.env.*") then
+ -- disable for .env files
+ return false
+ end
+ return true
+ end,
+ svn = false,
+ yaml = false,
+ ["."] = false,
+ ["*"] = true,
+ },
+ copilot_node_command = "node", -- Node.js version must be > 18.x
+ server_opts_overrides = {},
+ })
+
+ local cmp = require("cmp")
+ cmp.event:on("menu_opened", function()
+ vim.b.copilot_suggestion_hidden = true
+ end)
+
+ cmp.event:on("menu_closed", function()
+ vim.b.copilot_suggestion_hidden = false
+ end)
+ end,
+
+ vim.keymap.set("n", "<leader>ct", function()
+ require("copilot.suggestion").toggle_auto_trigger()
+ end, { noremap = true, silent = true, desc = "Toggle copilot" }),
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cloak.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cloak.lua
new file mode 100644
index 0000000..09b14d8
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cloak.lua
@@ -0,0 +1,42 @@
+return {
+ "laytan/cloak.nvim",
+ lazy = false,
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>c", group = "Cloak" },
+ })
+ end,
+ config = function()
+ require("cloak").setup({
+ enabled = true,
+ cloak_character = "*",
+ -- The applied highlight group (colors) on the cloaking, see `:h highlight`.
+ highlight_group = "Comment",
+ patterns = {
+ {
+ -- Match any file starting with ".env".
+ -- This can be a table to match multiple file patterns.
+ file_pattern = {
+ ".env*",
+ "wrangler.toml",
+ ".dev.vars",
+ "address*",
+ "*api*",
+ },
+ -- Match an equals sign and any character after it.
+ -- This can also be a table of patterns to cloak,
+ -- example: cloak_pattern = { ":.+", "-.+" } for yaml files.
+ cloak_pattern = { "=.+", ":.+", "-.+" },
+ },
+ },
+ })
+ end,
+ keys = {
+ { "<leader>ce", "<cmd>CloakEnable<cr>", desc = "Enable cloak" },
+ { "<leader>cd", "<cmd>CloakDisable<cr>", desc = "Disable cloak" },
+ { "<leader>cl", "<cmd>CloakPreviewLine<cr>", desc = "Preview line cloak" },
+ { "<leader>zC", "<cmd>CloakToggle<cr>", desc = "Toggle cloak" },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cmp.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cmp.lua
new file mode 100644
index 0000000..276215d
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/cmp.lua
@@ -0,0 +1,326 @@
+local trigger_text = ";"
+
+return {
+ {
+ "hrsh7th/nvim-cmp",
+ event = "InsertEnter",
+ dependencies = {
+ "hrsh7th/cmp-buffer", -- source for text in buffer
+ "hrsh7th/cmp-path", -- source for file system paths
+ {
+ "L3MON4D3/LuaSnip",
+ version = "v2.*", -- Replace <CurrentMajor> by the latest released major (first number of latest release)
+ build = "make install_jsregexp",
+ },
+ "saadparwaiz1/cmp_luasnip", -- for autocompletion
+ "rafamadriz/friendly-snippets", -- useful snippets
+ "onsails/lspkind.nvim", -- vs-code like pictograms
+ "uga-rosa/cmp-dictionary", -- dictionary & spell
+ },
+ config = function()
+ local cmp = require("cmp")
+ local luasnip = require("luasnip")
+ local has_words_before = function()
+ unpack = unpack or table.unpack
+ local line, col = unpack(vim.api.nvim_win_get_cursor(0))
+ return col ~= 0
+ and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
+ end
+
+ require("luasnip.loaders.from_vscode").lazy_load()
+
+ cmp.setup({
+ completion = {
+ completeopt = "menu,menuone,preview,noselect",
+ },
+ snippet = { -- configure how nvim-cmp interacts with snippet engine
+ expand = function(args)
+ require("luasnip").lsp_expand(args.body)
+ end,
+ },
+ mapping = cmp.mapping.preset.insert({
+ ["<C-p>"] = cmp.mapping.select_prev_item(), -- previous suggestion
+ ["<C-n>"] = cmp.mapping.select_next_item(), -- next suggestion
+ ["<C-d>"] = cmp.mapping.scroll_docs(-4),
+ ["<C-u>"] = cmp.mapping.scroll_docs(4),
+ ["<C-Space>"] = cmp.mapping.complete(), -- show completion suggestions
+ ["<C-x>"] = cmp.mapping.abort(), -- close completion window
+ ["<C-c>"] = cmp.mapping.close(),
+ ["<CR>"] = cmp.mapping.confirm({
+ behavior = cmp.ConfirmBehavior.Replace,
+ select = true,
+ }),
+ ["<Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ -- You could replace the expand_or_jumpable() calls with expand_or_locally_jumpable()
+ -- this way you will only jump inside the snippet region
+ elseif luasnip.expand_or_jumpable() then
+ luasnip.expand_or_jump()
+ elseif has_words_before() then
+ cmp.complete()
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ }),
+ -- sources for autocompletion
+ sources = cmp.config.sources({
+ { name = "buffer" }, -- text within current buffer
+ { name = "crates" },
+ { name = "copilot" },
+ { name = "dictionary", keyword_length = 2 },
+ { name = "emoji" },
+ { name = "luasnip" }, -- snippets
+ { name = "nvim_lsp" },
+ { name = "nvim_lua", priority = 100 },
+ { name = "path" }, -- file system paths
+ { name = "projects", priority = 100 },
+ { name = "snippets" },
+ { name = "vim-dadbod-completion" }, -- Enable dadbod completion source
+ { name = "vsnip" },
+ }),
+
+ -- configure lspkind for vs-code like pictograms in completion menu
+ formatting = {
+ expandable_indicator = true,
+ fields = {
+ "abbr",
+ "kind",
+ "menu",
+ },
+ format = require("lspkind").cmp_format({
+ mode = "symbol_text",
+ maxwidth = 50,
+ ellipsis_char = "...",
+ menu = {
+ buffer = "[Buffer]",
+ luasnip = "[LuaSnip]",
+ nvim_lsp = "[LSP]",
+ nvim_lua = "[Lua]",
+ projects = "[Projects]",
+ emoji = "[Emoji]",
+ vsnip = "[Snippet]",
+ },
+ }),
+ },
+ })
+
+ -- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore).
+ cmp.setup.cmdline({ "/", "?" }, {
+ mapping = cmp.mapping.preset.cmdline(),
+ sources = {
+ { name = "buffer" },
+ },
+ })
+
+ -- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
+ cmp.setup.cmdline(":", {
+ mapping = cmp.mapping.preset.cmdline(),
+ sources = cmp.config.sources({
+ { name = "path" },
+ }, {
+ { name = "cmdline" },
+ }),
+ matching = { disallow_symbol_nonprefix_matching = false },
+ })
+
+ -- sql
+ cmp.setup.filetype({ "sql" }, {
+ sources = {
+ { name = "vim-dadbod-completion" },
+ { name = "buffer" },
+ },
+ })
+
+ local lspkind = require("lspkind")
+ lspkind.init({
+ symbol_map = {
+ Copilot = "",
+ },
+ })
+
+ vim.api.nvim_set_hl(0, "CmpItemKindCopilot", { fg = "#6CC644" })
+
+ require("cmp_dictionary").setup({
+ paths = {
+ "/usr/share/dict/words",
+ vim.fn.expand(vim.fn.stdpath("config") .. "/lua/thesiahxyz/spells/en.utf-8.add"),
+ },
+ exact_length = 2,
+ })
+ end,
+ },
+ -- {
+ -- "saghen/blink.cmp",
+ -- version = "*",
+ -- -- build = "cargo build --release",
+ -- opts_extend = {
+ -- "sources.completion.enabled_providers",
+ -- "sources.compat",
+ -- "sources.default",
+ -- },
+ -- enabled = true,
+ -- dependencies = {
+ -- {
+ -- "L3MON4D3/LuaSnip",
+ -- version = "v2.*",
+ -- build = "make install_jsregexp",
+ -- },
+ -- "rafamadriz/friendly-snippets",
+ -- {
+ -- "saghen/blink.compat",
+ -- optional = true, -- make optional so it's only enabled if any extras need it
+ -- opts = {},
+ -- version = "*",
+ -- },
+ -- "kristjanhusak/vim-dadbod-completion",
+ -- "giuxtaposition/blink-cmp-copilot",
+ -- },
+ -- event = "InsertEnter",
+ -- opts = function(_, opts)
+ -- opts.sources = vim.tbl_deep_extend("force", opts.sources or {}, {
+ -- default = { "lsp", "path", "snippets", "buffer", "copilot", "luasnip", "dadbod" },
+ -- providers = {
+ -- lsp = {
+ -- name = "lsp",
+ -- enabled = true,
+ -- module = "blink.cmp.sources.lsp",
+ -- fallbacks = { "snippets", "luasnip", "buffer" },
+ -- score_offset = 90, -- the higher the number, the higher the priority
+ -- },
+ -- luasnip = {
+ -- name = "luasnip",
+ -- enabled = true,
+ -- module = "blink.cmp.sources.luasnip",
+ -- min_keyword_length = 2,
+ -- fallbacks = { "snippets" },
+ -- score_offset = 85,
+ -- max_items = 8,
+ -- },
+ -- path = {
+ -- name = "Path",
+ -- module = "blink.cmp.sources.path",
+ -- score_offset = 3,
+ -- -- When typing a path, I would get snippets and text in the
+ -- -- suggestions, I want those to show only if there are no path
+ -- -- suggestions
+ -- fallbacks = { "snippets", "luasnip", "buffer" },
+ -- opts = {
+ -- trailing_slash = false,
+ -- label_trailing_slash = true,
+ -- get_cwd = function(context)
+ -- return vim.fn.expand(("#%d:p:h"):format(context.bufnr))
+ -- end,
+ -- show_hidden_files_by_default = true,
+ -- },
+ -- },
+ -- buffer = {
+ -- name = "Buffer",
+ -- enabled = true,
+ -- max_items = 3,
+ -- module = "blink.cmp.sources.buffer",
+ -- min_keyword_length = 4,
+ -- },
+ -- snippets = {
+ -- name = "Snippets",
+ -- enabled = true,
+ -- max_items = 3,
+ -- module = "blink.cmp.sources.snippets",
+ -- min_keyword_length = 4,
+ -- score_offset = 80, -- the higher the number, the higher the priority
+ -- },
+ -- -- Example on how to configure dadbod found in the main repo
+ -- -- https://github.com/kristijanhusak/vim-dadbod-completion
+ -- dadbod = {
+ -- name = "Dadbod",
+ -- module = "vim_dadbod_completion.blink",
+ -- score_offset = 85, -- the higher the number, the higher the priority
+ -- },
+ -- -- Third class citizen mf always talking shit
+ -- copilot = {
+ -- name = "Copilot",
+ -- enabled = true,
+ -- module = "blink-cmp-copilot",
+ -- min_keyword_length = 6,
+ -- score_offset = -100, -- the higher the number, the higher the priority
+ -- async = true,
+ -- },
+ -- },
+ -- -- command line completion, thanks to dpetka2001 in reddit
+ -- -- https://www.reddit.com/r/neovim/comments/1hjjf21/comment/m37fe4d/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
+ -- cmdline = function()
+ -- local type = vim.fn.getcmdtype()
+ -- if type == "/" or type == "?" then
+ -- return { "buffer" }
+ -- end
+ -- if type == ":" then
+ -- return { "cmdline" }
+ -- end
+ -- return {}
+ -- end,
+ -- })
+ --
+ -- -- This comes from the luasnip extra, if you don't add it, won't be able to
+ -- -- jump forward or backward in luasnip snippets
+ -- opts.snippets = {
+ -- expand = function(snippet)
+ -- require("luasnip").lsp_expand(snippet)
+ -- end,
+ -- active = function(filter)
+ -- if filter and filter.direction then
+ -- return require("luasnip").jumpable(filter.direction)
+ -- end
+ -- return require("luasnip").in_snippet()
+ -- end,
+ -- jump = function(direction)
+ -- require("luasnip").jump(direction)
+ -- end,
+ -- }
+ --
+ -- opts.appearance = {
+ -- -- sets the fallback highlight groups to nvim-cmp's highlight groups
+ -- -- useful for when your theme doesn't support blink.cmp
+ -- -- will be removed in a future release, assuming themes add support
+ -- use_nvim_cmp_as_default = false,
+ -- -- set to 'mono' for 'Nerd Font Mono' or 'normal' for 'Nerd Font'
+ -- -- adjusts spacing to ensure icons are aligned
+ -- nerd_font_variant = "mono",
+ -- }
+ --
+ -- opts.completion = {
+ -- accept = {
+ -- -- experimental auto-brackets support
+ -- auto_brackets = {
+ -- enabled = true,
+ -- },
+ -- },
+ -- menu = {
+ -- draw = {
+ -- treesitter = { "lsp" },
+ -- },
+ -- },
+ -- documentation = {
+ -- auto_show = true,
+ -- auto_show_delay_ms = 200,
+ -- },
+ -- ghost_text = { enabled = true },
+ -- }
+ --
+ -- opts.keymap = {
+ -- preset = "super-tab",
+ -- }
+ --
+ -- return opts
+ -- end,
+ -- },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/colorschemes.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/colorschemes.lua
new file mode 100644
index 0000000..dd6296d
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/colorschemes.lua
@@ -0,0 +1,268 @@
+function ColorMyPencils(color)
+ color = color or "catppuccin"
+ vim.cmd.colorscheme(color)
+ vim.api.nvim_set_hl(0, "Normal", { bg = "NONE" })
+ vim.api.nvim_set_hl(0, "NormalFloat", { bg = "NONE" })
+end
+
+return {
+ { "junegunn/seoul256.vim" },
+ {
+ "catppuccin/nvim",
+ name = "catppuccin",
+ config = function()
+ require("catppuccin").setup({
+ flavour = "auto", -- latte, frappe, macchiato, mocha
+ background = { -- :h background
+ light = "latte",
+ dark = "mocha",
+ },
+ transparent_background = true, -- disables setting the background color.
+ show_end_of_buffer = false, -- shows the '~' characters after the end of buffers
+ term_colors = false, -- sets terminal colors (e.g. `g:terminal_color_0`)
+ dim_inactive = {
+ enabled = true, -- dims the background color of inactive window
+ shade = "dark",
+ percentage = 0.15, -- percentage of the shade to apply to the inactive window
+ },
+ no_italic = false, -- Force no italic
+ no_bold = false, -- Force no bold
+ no_underline = false, -- Force no underline
+ styles = { -- Handles the styles of general hi groups (see `:h highlight-args`):
+ comments = { "italic" }, -- Change the style of comments
+ conditionals = { "italic" },
+ loops = {},
+ functions = {},
+ keywords = {},
+ strings = {},
+ variables = {},
+ numbers = {},
+ booleans = {},
+ properties = {},
+ types = {},
+ operators = {},
+ -- miscs = {}, -- Uncomment to turn off hard-coded styles
+ },
+ color_overrides = {},
+ custom_highlights = {},
+ default_integrations = true,
+ integrations = {
+ aerial = true,
+ alpha = true,
+ blink_cmp = true,
+ cmp = true,
+ dashboard = true,
+ flash = true,
+ gitsigns = true,
+ headlines = true,
+ illuminate = true,
+ indent_blankline = { enabled = true, scope_color = "peach", colored_indent_levels = true },
+ leap = true,
+ lsp_trouble = true,
+ mason = true,
+ markdown = true,
+ mini = true,
+ native_lsp = {
+ enabled = true,
+ underlines = {
+ errors = { "undercurl" },
+ hints = { "undercurl" },
+ warnings = { "undercurl" },
+ information = { "undercurl" },
+ },
+ },
+ navic = { enabled = true, custom_bg = "NONE" },
+ neotest = true,
+ neotree = true,
+ noice = true,
+ notify = true,
+ semantic_tokens = true,
+ telescope = true,
+ treesitter = true,
+ treesitter_context = true,
+ which_key = true,
+ },
+ highlight_overrides = {
+ mocha = function(mocha)
+ return {
+ LineNr = { fg = mocha.overlay2 },
+ CursorLineNr = { fg = mocha.sky },
+ Normal = { bg = "NONE" }, -- normal text
+ NormalNC = { bg = "NONE" },
+ }
+ end,
+ },
+ })
+
+ ColorMyPencils()
+ end,
+ },
+ {
+ "ellisonleao/gruvbox.nvim",
+ priority = 1000,
+ -- opts = ...,
+ config = function()
+ require("gruvbox").setup({
+ terminal_colors = true, -- add neovim terminal colors
+ undercurl = true,
+ underline = true,
+ bold = true,
+ italic = {
+ strings = true,
+ emphasis = true,
+ comments = true,
+ operators = false,
+ folds = true,
+ },
+ strikethrough = true,
+ invert_selection = false,
+ invert_signs = false,
+ invert_tabline = false,
+ invert_intend_guides = false,
+ inverse = true, -- invert background for search, diffs, statuslines and errors
+ contrast = "", -- can be "hard", "soft" or empty string
+ palette_overrides = {
+ dark1 = "NONE",
+ },
+ overrides = {},
+ dim_inactive = true,
+ transparent_mode = true,
+ })
+ end,
+ },
+ {
+ "rose-pine/neovim",
+ name = "rose-pine",
+ config = function()
+ require("rose-pine").setup({
+ variant = "auto", -- auto, main, moon, or dawn
+ dark_variant = "main", -- main, moon, or dawn
+ dim_inactive_windows = false,
+ extend_background_behind_borders = true,
+ enable = {
+ terminal = true,
+ legacy_highlights = true, -- Improve compatibility for previous versions of Neovim
+ migrations = true, -- Handle deprecated options automatically
+ },
+ styles = {
+ bold = true,
+ italic = false,
+ transparency = true,
+ },
+ groups = {
+ border = "muted",
+ link = "iris",
+ panel = "surface",
+
+ error = "love",
+ hint = "iris",
+ info = "foam",
+ note = "pine",
+ todo = "rose",
+ warn = "gold",
+
+ git_add = "foam",
+ git_change = "rose",
+ git_delete = "love",
+ git_dirty = "rose",
+ git_ignore = "muted",
+ git_merge = "iris",
+ git_rename = "pine",
+ git_stage = "iris",
+ git_text = "rose",
+ git_untracked = "subtle",
+
+ h1 = "iris",
+ h2 = "foam",
+ h3 = "rose",
+ h4 = "gold",
+ h5 = "pine",
+ h6 = "foam",
+ },
+ palette = {
+ -- Override the builtin palette per variant
+ -- moon = {
+ -- base = '#18191a',
+ -- overlay = '#363738',
+ -- },
+ },
+ highlight_groups = {
+ -- Comment = { fg = "foam" },
+ -- VertSplit = { fg = "muted", bg = "muted" },
+ },
+ before_highlight = function(group, highlight, palette)
+ -- Disable all undercurls
+ -- if highlight.undercurl then
+ -- highlight.undercurl = false
+ -- end
+ --
+ -- Change palette colour
+ -- if highlight.fg == palette.pine then
+ -- highlight.fg = palette.foam
+ -- end
+ end,
+ })
+ -- vim.cmd("colorscheme rose-pine")
+ -- vim.cmd("colorscheme rose-pine-main")
+ -- vim.cmd("colorscheme rose-pine-moon")
+ -- vim.cmd("colorscheme rose-pine-dawn")
+ end,
+ },
+ {
+ "Mofiqul/dracula.nvim",
+ config = function()
+ local dracula = require("dracula")
+ dracula.setup({
+ -- customize dracula color palette
+ colors = {
+ bg = "#282A36",
+ fg = "#F8F8F2",
+ selection = "#44475A",
+ comment = "#6272A4",
+ red = "#FF5555",
+ orange = "#FFB86C",
+ yellow = "#F1FA8C",
+ green = "#50fa7b",
+ purple = "#BD93F9",
+ cyan = "#8BE9FD",
+ pink = "#FF79C6",
+ bright_red = "#FF6E6E",
+ bright_green = "#69FF94",
+ bright_yellow = "#FFFFA5",
+ bright_blue = "#D6ACFF",
+ bright_magenta = "#FF92DF",
+ bright_cyan = "#A4FFFF",
+ bright_white = "#FFFFFF",
+ menu = "#21222C",
+ visual = "#3E4452",
+ gutter_fg = "#4B5263",
+ nontext = "#3B4048",
+ white = "#ABB2BF",
+ black = "#191A21",
+ },
+ -- show the '~' characters after the end of buffers
+ show_end_of_buffer = true, -- default false
+ -- use transparent background
+ transparent_bg = true, -- default false
+ -- set custom lualine background color
+ lualine_bg_color = "#44475a", -- default nil
+ -- set italic comment
+ italic_comment = true, -- default false
+ -- overrides the default highlights with table see `:h synIDattr`
+ overrides = {},
+ -- You can use overrides as table like this
+ -- overrides = {
+ -- NonText = { fg = "white" }, -- set NonText fg to white
+ -- NvimTreeIndentMarker = { link = "NonText" }, -- link to NonText highlight
+ -- Nothing = {} -- clear highlight of Nothing
+ -- },
+ -- Or you can also use it like a function to get color from theme
+ -- overrides = function (colors)
+ -- return {
+ -- NonText = { fg = colors.white }, -- set NonText fg to white of theme
+ -- }
+ -- end,
+ })
+ end,
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/comment.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/comment.lua
new file mode 100644
index 0000000..fe82b02
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/comment.lua
@@ -0,0 +1,50 @@
+return {
+ {
+ "numToStr/Comment.nvim",
+ lazy = false,
+ opts = {},
+ config = function()
+ require("Comment").setup()
+ end,
+ },
+ {
+ "folke/todo-comments.nvim",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ opts = {},
+ cmd = { "TodoTrouble", "TodoTelescope" },
+ config = function()
+ require("todo-comments").setup()
+ end,
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>t", group = "TODO" },
+ })
+ end,
+ keys = {
+ {
+ "]t",
+ function()
+ require("todo-comments").jump_next()
+ end,
+ desc = "Next Todo Comment",
+ },
+ {
+ "[t",
+ function()
+ require("todo-comments").jump_prev()
+ end,
+ desc = "Previous Todo Comment",
+ },
+ { "<leader>tt", "<cmd>Trouble todo toggle<cr>", desc = "Toggle TODO (Trouble)" },
+ {
+ "<leader>tT",
+ "<cmd>Trouble todo toggle filter = {tag = {TODO,FIX,FIXME}}<cr>",
+ desc = "Toggle Todo/Fix/Fixme (Trouble)",
+ },
+ { "<leader>ft", "<cmd>TodoTelescope<cr>", desc = "Find Todo" },
+ { "<leader>fT", "<cmd>TodoTelescope keywords=TODO,FIX,FIXME<cr>", desc = "Find Todo/Fix/Fixme" },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/compiler.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/compiler.lua
new file mode 100644
index 0000000..d760594
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/compiler.lua
@@ -0,0 +1,53 @@
+return {
+ { -- This plugin
+ "Zeioth/compiler.nvim",
+ cmd = { "CompilerOpen", "CompilerToggleResults", "CompilerRedo" },
+ dependencies = { "stevearc/overseer.nvim", "nvim-telescope/telescope.nvim" },
+ opts = {},
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>r", group = "Compiler/Refactoring" },
+ })
+ end,
+ keys = {
+ -- Open compiler
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>ro",
+ "<cmd>CompilerOpen<cr>",
+ { noremap = true, silent = true, desc = "Open compiler" }
+ ),
+
+ -- Redo last selected option
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>rc",
+ "<cmd>CompilerStop<cr>" -- (Optional, to dispose all tasks before redo)
+ .. "<cmd>CompilerRedo<cr>",
+ { noremap = true, silent = true, desc = "Recompile" }
+ ),
+ -- Toggle compiler results
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>rt",
+ "<cmd>CompilerToggleResults<cr>",
+ { noremap = true, silent = true, desc = "Toggle compiler" }
+ ),
+ },
+ },
+ { -- The task runner we use
+ "stevearc/overseer.nvim",
+ commit = "6271cab7ccc4ca840faa93f54440ffae3a3918bd",
+ cmd = { "CompilerOpen", "CompilerToggleResults", "CompilerRedo" },
+ opts = {
+ task_list = {
+ direction = "bottom",
+ min_height = 25,
+ max_height = 25,
+ default_detail = 1,
+ },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/context.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/context.lua
new file mode 100644
index 0000000..e8979c8
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/context.lua
@@ -0,0 +1,26 @@
+return {
+ "nvim-treesitter/nvim-treesitter-context",
+ cmd = { "TSContextEnable", "TSContextDisable", "TSContextToggle" },
+ config = function()
+ require("treesitter-context").setup({
+ enable = false, -- Enable this plugin (Can be enabled/disabled later via commands)
+ max_lines = 3, -- How many lines the window should span. Values <= 0 mean no limit.
+ min_window_height = 1, -- Minimum editor window height to enable context. Values <= 0 mean no limit.
+ line_numbers = true,
+ multiline_threshold = 20, -- Maximum number of lines to show for a single context
+ trim_scope = "outer", -- Which context lines to discard if `max_lines` is exceeded. Choices: 'inner', 'outer'
+ mode = "cursor", -- Line used to calculate context. Choices: 'cursor', 'topline'
+ -- Separator between context and content. Should be a single character string, like '-'.
+ -- When separator is set, the context will only show up when there are at least 2 lines above cursorline.
+ separator = nil,
+ zindex = 20, -- The Z-index of the context window
+ on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching
+ })
+ end,
+ keys = {
+ vim.keymap.set("n", "[t", function()
+ require("treesitter-context").go_to_context(vim.v.count1)
+ end, { silent = true, desc = "Go to context" }),
+ vim.keymap.set({ "n", "v" }, "<leader>zc", "<cmd>TSContextToggle<cr>", { desc = "Toggle context" }),
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/csv.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/csv.lua
new file mode 100644
index 0000000..0805d1d
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/csv.lua
@@ -0,0 +1,88 @@
+return {
+ {
+ "cameron-wags/rainbow_csv.nvim",
+ config = function()
+ require("rainbow_csv").setup()
+ -- vim.g.rcsv_colorpairs = {
+ -- { "red", "red" },
+ -- { "blue", "blue" },
+ -- { "green", "green" },
+ -- { "magenta", "magenta" },
+ -- { "NONE", "NONE" },
+ -- { "darkred", "darkred" },
+ -- { "darkblue", "darkblue" },
+ -- { "darkgreen", "darkgreen" },
+ -- { "darkmagenta", "darkmagenta" },
+ -- { "darkcyan", "darkcyan" },
+ -- }
+ end,
+ ft = {
+ "csv",
+ "tsv",
+ "csv_semicolon",
+ "csv_whitespace",
+ "csv_pipe",
+ "rfc_csv",
+ "rfc_semicolon",
+ },
+ cmd = {
+ "RainbowDelim",
+ "RainbowDelimSimple",
+ "RainbowDelimQuoted",
+ "RainbowMultiDelim",
+ },
+ },
+ {
+ "hat0uma/csvview.nvim",
+ cmd = { "CsvViewEnable", "CsvViewDisable", "CsvViewToggle" },
+ event = { "BufReadPre *.csv" }, -- Lazy-load the plugin when a CSV file is about to be read
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>cs", group = "csv" },
+ })
+ end,
+ config = function()
+ require("csvview").setup()
+
+ vim.api.nvim_create_autocmd("BufRead", {
+ pattern = "*.csv",
+ callback = function()
+ vim.cmd("CsvViewEnable")
+ end,
+ })
+
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = "csv",
+ callback = function()
+ vim.keymap.set(
+ "n",
+ "<leader>zv",
+ "<cmd>CsvViewToggle<cr>",
+ { desc = "Toggle CSV view", buffer = true }
+ )
+ end,
+ })
+ end,
+ keys = {
+ {
+ "<leader>csv",
+ function()
+ local delimiter = vim.fn.input("Delimiter (e.g., ,): ")
+ local quote_char = vim.fn.input("Quote char (e.g., '): ")
+ local comment = vim.fn.input("Comment char (e.g., #): ")
+ local command = string.format(
+ ":CsvViewToggle delimiter=%s quote_char=%s comment=%s<CR>",
+ delimiter,
+ quote_char,
+ comment
+ )
+
+ vim.cmd(command)
+ end,
+ desc = "Toggle CSV view",
+ },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dadbod.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dadbod.lua
new file mode 100644
index 0000000..41adb5a
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dadbod.lua
@@ -0,0 +1,52 @@
+return {
+ "kristijanhusak/vim-dadbod-ui",
+ dependencies = {
+ { "tpope/vim-dadbod", lazy = true },
+ { "kristijanhusak/vim-dadbod-completion", ft = { "sql", "mysql", "plsql" }, lazy = true },
+ },
+ cmd = {
+ "DBUI",
+ "DBUIToggle",
+ "DBUIAddConnection",
+ "DBUIFindBuffer",
+ },
+ init = function()
+ -- Your DBUI configuration
+ vim.g.db_ui_use_nerd_fonts = 1
+ local home = vim.fn.expand("~")
+ vim.g.dbs = {
+ firefox = "sqlite://" .. home .. "/.mozilla/firefox/si.default/places.sqlite",
+ mysql = "mariadb://user:password@localhost/mysql",
+ postsql = "postgresql://postgres:mypassword@localhost:5432/postgresql",
+ sqlite = "sqlite://" .. home .. "/.local/share/db/sqlite.db",
+ }
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<localleader>d", group = "DB" },
+ })
+ end,
+ config = function()
+ local function db_completion()
+ require("cmp").setup.buffer({ sources = { { name = "vim-dadbod-completion" } } })
+ end
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = {
+ "sql",
+ "mysql",
+ "plsql",
+ },
+ callback = function()
+ vim.schedule(db_completion)
+ end,
+ })
+ end,
+ keys = {
+ { "<localleader>du", "<cmd>DBUI<cr>", desc = "DB UI" },
+ { "<localleader>dt", "<cmd>DBUIToggle<cr>", desc = "Toggle DB UI" },
+ { "<localleader>da", "<cmd>DBUIAddConnection<cr>", desc = "Add connection" },
+ { "<localleader>df", "<cmd>DBUIFindBuffer<cr>", desc = "Find buffer" },
+ { "<localleader>dr", "<cmd>DBUIRenameBuffer<cr>", desc = "Rename buffer" },
+ { "<localleader>di", "<cmd>DBUILastQueryInfo<cr>", desc = "Last query info" },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dap.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dap.lua
new file mode 100644
index 0000000..d1ef5d7
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/dap.lua
@@ -0,0 +1,295 @@
+local function get_args(config)
+ local args = type(config.args) == "function" and (config.args() or {}) or config.args or {}
+ config = vim.deepcopy(config)
+ ---@cast args string[]
+ config.args = function()
+ local new_args = vim.fn.input("Run with args: ", table.concat(args, " ")) --[[@as string]]
+ return vim.split(vim.fn.expand(new_args) --[[@as string]], " ")
+ end
+ return config
+end
+
+return {
+ {
+ "mfussenegger/nvim-dap",
+ recommended = true,
+ desc = "Debugging support. Requires language specific adapters to be configured. (see lang extras)",
+ dependencies = {
+ "rcarriga/nvim-dap-ui",
+ -- virtual text for the debugger
+ {
+ "theHamsta/nvim-dap-virtual-text",
+ opts = {},
+ },
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<localleader>dp", group = "Debug" },
+ { "<localleader>dP", group = "Debug (Python)" },
+ })
+ end,
+ config = function()
+ -- load mason-nvim-dap here, after all adapters have been setup
+ vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" })
+
+ local dap = require("dap")
+ dap.configurations.java = {
+ {
+ type = "java",
+ request = "attach",
+ name = "Debug (Attach) - Remote",
+ hostName = "127.0.0.1",
+ port = 5005,
+ },
+ }
+
+ local dap_icons = {
+ Stopped = { "󰁕 ", "DiagnosticWarn", "DapStoppedLine" },
+ Breakpoint = " ",
+ BreakpointCondition = " ",
+ BreakpointRejected = { " ", "DiagnosticError" },
+ LogPoint = ".>",
+ }
+
+ for name, sign in pairs(dap_icons) do
+ sign = type(sign) == "table" and sign or { sign }
+ vim.fn.sign_define(
+ "Dap" .. name,
+ { text = sign[1], texthl = sign[2] or "DiagnosticInfo", linehl = sign[3], numhl = sign[3] }
+ )
+ end
+
+ -- setup dap config by VsCode launch.json file
+ local vscode = require("dap.ext.vscode")
+ local json = require("plenary.json")
+ vscode.json_decode = function(str)
+ return vim.json.decode(json.json_strip_comments(str))
+ end
+ end,
+ keys = {
+ {
+ "<localleader>dpB",
+ function()
+ require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: "))
+ end,
+ desc = "Dap breakpoint condition",
+ },
+ {
+ "<localleader>dpb",
+ function()
+ require("dap").toggle_breakpoint()
+ end,
+ desc = "Dap toggle breakpoint",
+ },
+ {
+ "<localleader>dpc",
+ function()
+ require("dap").continue()
+ end,
+ desc = "Dap continue",
+ },
+ {
+ "<localleader>dpa",
+ function()
+ require("dap").continue({ before = get_args })
+ end,
+ desc = "Dap run with args",
+ },
+ {
+ "<localleader>dpC",
+ function()
+ require("dap").run_to_cursor()
+ end,
+ desc = "Dap run to cursor",
+ },
+ {
+ "<localleader>dpg",
+ function()
+ require("dap").goto_()
+ end,
+ desc = "Dap go to line (no execute)",
+ },
+ {
+ "<localleader>dpi",
+ function()
+ require("dap").step_into()
+ end,
+ desc = "Dap step into",
+ },
+ {
+ "<localleader>dpj",
+ function()
+ require("dap").down()
+ end,
+ desc = "Dap down",
+ },
+ {
+ "<localleader>dpk",
+ function()
+ require("dap").up()
+ end,
+ desc = "Dap up",
+ },
+ {
+ "<localleader>dpl",
+ function()
+ require("dap").run_last()
+ end,
+ desc = "Dap run last",
+ },
+ {
+ "<localleader>dpo",
+ function()
+ require("dap").step_out()
+ end,
+ desc = "Dap step out",
+ },
+ {
+ "<localleader>dpO",
+ function()
+ require("dap").step_over()
+ end,
+ desc = "Dap step over",
+ },
+ {
+ "<localleader>dpp",
+ function()
+ require("dap").pause()
+ end,
+ desc = "Dap pause",
+ },
+ {
+ "<localleader>dpr",
+ function()
+ require("dap").repl.toggle()
+ end,
+ desc = "Dap toggle repl",
+ },
+ {
+ "<localleader>dps",
+ function()
+ require("dap").session()
+ end,
+ desc = "Dap session",
+ },
+ {
+ "<localleader>dpt",
+ function()
+ require("dap").terminate()
+ end,
+ desc = "Dap terminate",
+ },
+ {
+ "<localleader>dpw",
+ function()
+ require("dap.ui.widgets").hover()
+ end,
+ desc = "Dap widgets",
+ },
+ {
+ "<localleader>dpR",
+ "<cmd>lua require('dapui').open({ reset = true })<cr>",
+ desc = "Dap UI reset",
+ },
+ },
+ },
+ {
+ "mfussenegger/nvim-dap-python",
+ ft = "python",
+ dependencies = { "mfussenegger/nvim-dap", "rcarriga/nvim-dap-ui" },
+ config = function()
+ local path = "~/.local/share/nvim/mason/packages/debugpy/venv/bin/python"
+ require("dap-python").setup(path)
+ end,
+ keys = {
+ {
+ "<localleader>dPt",
+ function()
+ require("dap-python").test_method()
+ end,
+ desc = "Dap debug method",
+ ft = "python",
+ },
+ {
+ "<localleader>dPc",
+ function()
+ require("dap-python").test_class()
+ end,
+ desc = "Dap debug class",
+ ft = "python",
+ },
+ },
+ },
+ {
+ "rcarriga/nvim-dap-ui",
+ dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" },
+ config = function()
+ local dap = require("dap")
+ local dapui = require("dapui")
+ dapui.setup()
+
+ dap.listeners.before.attach.dapui_config = function()
+ dapui.open()
+ end
+ dap.listeners.before.launch.dapui_config = function()
+ dapui.open()
+ end
+ dap.listeners.before.event_terminated.dapui_config = function()
+ dapui.close()
+ end
+ dap.listeners.before.event_exited.dapui_config = function()
+ dapui.close()
+ end
+
+ dap.listeners.after.event_initialized["dapui_config"] = function()
+ dapui.open()
+ end
+ dap.listeners.after.event_terminated["dapui_config"] = function()
+ dapui.close()
+ end
+ dap.listeners.after.event_exited["dapui_config"] = function()
+ dapui.close()
+ end
+ end,
+ keys = {
+ {
+ "<localleader>dpu",
+ function()
+ require("dapui").toggle()
+ end,
+ desc = "Dap UI",
+ },
+ {
+ "<localleader>dpe",
+ function()
+ require("dapui").eval()
+ end,
+ desc = "Dap eval",
+ },
+ },
+ },
+ {
+ "jay-babu/mason-nvim-dap.nvim",
+ dependencies = "mason.nvim",
+ cmd = { "DapInstall", "DapUninstall" },
+ opts = {
+ -- Makes a best effort to setup the various debuggers with
+ -- reasonable debug configurations
+ automatic_installation = true,
+
+ -- You can provide additional configuration to the handlers,
+ -- see mason-nvim-dap README for more information
+ handlers = {},
+
+ -- You'll need to check that you have the required things installed
+ -- online, please don't ask me how to install them :)
+ ensure_installed = {
+ -- Update this to ensure that you have the debuggers for the langs you want
+ },
+ },
+ -- mason-nvim-dap is loaded when nvim-dap loads
+ config = function() end,
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/docker.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/docker.lua
new file mode 100644
index 0000000..7bc26d5
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/docker.lua
@@ -0,0 +1,222 @@
+return {
+ "https://codeberg.org/esensar/nvim-dev-container",
+ dependencies = "nvim-treesitter/nvim-treesitter",
+ config = function()
+ require("devcontainer").setup({
+ config_search_start = function()
+ -- By default this function uses vim.loop.cwd()
+ -- This is used to find a starting point for .devcontainer.json file search
+ -- Since by default, it is searched for recursively
+ -- That behavior can also be disabled
+ end,
+ workspace_folder_provider = function()
+ -- By default this function uses first workspace folder for integrated lsp if available and vim.loop.cwd() as a fallback
+ -- This is used to replace `${localWorkspaceFolder}` in devcontainer.json
+ -- Also used for creating default .devcontainer.json file
+ end,
+ terminal_handler = function(command)
+ -- By default this function creates a terminal in a new tab using :terminal command
+ -- It also removes statusline when that tab is active, to prevent double statusline
+ -- It can be overridden to provide custom terminal handling
+ end,
+ nvim_installation_commands_provider = function(path_binaries, version_string)
+ -- Returns table - list of commands to run when adding neovim to container
+ -- Each command can either be a string or a table (list of command parts)
+ -- Takes binaries available in path on current container and version_string passed to the command or current version of neovim
+ end,
+ devcontainer_json_template = function()
+ -- Returns table - list of lines to set when creating new devcontainer.json files
+ -- As a template
+ -- Used only when using functions from commands module or created commands
+ end,
+ -- Can be set to false to prevent generating default commands
+ -- Default commands are listed below
+ generate_commands = true,
+ -- By default no autocommands are generated
+ -- This option can be used to configure automatic starting and cleaning of containers
+ autocommands = {
+ -- can be set to true to automatically start containers when devcontainer.json is available
+ init = false,
+ -- can be set to true to automatically remove any started containers and any built images when exiting vim
+ clean = false,
+ -- can be set to true to automatically restart containers when devcontainer.json file is updated
+ update = false,
+ },
+ -- can be changed to increase or decrease logging from library
+ log_level = "info",
+ -- can be set to true to disable recursive search
+ -- in that case only .devcontainer.json and .devcontainer/devcontainer.json files will be checked relative
+ -- to the directory provided by config_search_start
+ disable_recursive_config_search = false,
+ -- can be set to false to disable image caching when adding neovim
+ -- by default it is set to true to make attaching to containers faster after first time
+ cache_images = true,
+ -- By default all mounts are added (config, data and state)
+ -- This can be changed to disable mounts or change their options
+ -- This can be useful to mount local configuration
+ -- And any other mounts when attaching to containers with this plugin
+ attach_mounts = {
+ neovim_config = {
+ -- enables mounting local config to /root/.config/nvim in container
+ enabled = false,
+ -- makes mount readonly in container
+ options = { "readonly" },
+ },
+ neovim_data = {
+ -- enables mounting local data to /root/.local/share/nvim in container
+ enabled = false,
+ -- no options by default
+ options = {},
+ },
+ -- Only useful if using neovim 0.8.0+
+ neovim_state = {
+ -- enables mounting local state to /root/.local/state/nvim in container
+ enabled = false,
+ -- no options by default
+ options = {},
+ },
+ },
+ -- This takes a list of mounts (strings) that should always be added to every run container
+ -- This is passed directly as --mount option to docker command
+ -- Or multiple --mount options if there are multiple values
+ always_mount = {},
+ -- This takes a string (usually either "podman" or "docker") representing container runtime - "devcontainer-cli" is also partially supported
+ -- That is the command that will be invoked for container operations
+ -- If it is nil, plugin will use whatever is available (trying "podman" first)
+ container_runtime = nil,
+ -- Similar to container runtime, but will be used if main runtime does not support an action - useful for "devcontainer-cli"
+ backup_runtime = nil,
+ -- This takes a string (usually either "podman-compose" or "docker-compose") representing compose command - "devcontainer-cli" is also partially supported
+ -- That is the command that will be invoked for compose operations
+ -- If it is nil, plugin will use whatever is available (trying "podman-compose" first)
+ compose_command = nil,
+ -- Similar to compose command, but will be used if main command does not support an action - useful for "devcontainer-cli"
+ backup_compose_command = nil,
+ })
+ end,
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<leader>d", group = "Docker" },
+ { "<leader>db", group = "Build (docker)" },
+ { "<leader>dc", group = "Compose (docker)" },
+ { "<leader>do", group = "Open (docker)" },
+ { "<leader>dr", group = "Run (docker)" },
+ })
+ end,
+ keys = {
+ {
+ "<leader>dcu",
+ function()
+ require("devcontainer.commands").compose_up()
+ end,
+ desc = "Compose up (docker)",
+ },
+ {
+ "<leader>dcd",
+ function()
+ require("devcontainer.commands").compose_down()
+ end,
+ desc = "Compose down (docker)",
+ },
+ {
+ "<leader>dcD",
+ function()
+ require("devcontainer.commands").compose_rm()
+ end,
+ desc = "Compose remove (docker)",
+ },
+ {
+ "<leader>dbb",
+ function()
+ require("devcontainer.commands").docker_build()
+ end,
+ desc = "Build (docker)",
+ },
+ {
+ "<leader>dri",
+ function()
+ require("devcontainer.commands").docker_image_run()
+ end,
+ desc = "Image run (docker)",
+ },
+ {
+ "<leader>dbr",
+ function()
+ require("devcontainer.commands").docker_build_and_run()
+ end,
+ desc = "Build & run (docker)",
+ },
+ {
+ "<leader>dba",
+ function()
+ require("devcontainer.commands").docker_build_run_and_attach()
+ end,
+ desc = "Build & attach (docker)",
+ },
+ {
+ "<leader>ds",
+ function()
+ require("devcontainer.commands").start_auto()
+ end,
+ desc = "Start (docker)",
+ },
+ {
+ "<leader>da",
+ function()
+ require("devcontainer.commands").attach_auto()
+ end,
+ desc = "Attach (docker)",
+ },
+ {
+ "<leader>drr",
+ function()
+ require("devcontainer.commands").exec("devcontainer", "ls", { on_success = function(result) end })
+ end,
+ desc = "Execute (docker)",
+ },
+ {
+ "<leader>dx",
+ function()
+ require("devcontainer.commands").stop_auto()
+ end,
+ desc = "Stop (docker)",
+ },
+ {
+ "<leader>dX",
+ function()
+ require("devcontainer.commands").stop_all()
+ end,
+ desc = "Stop all (docker)",
+ },
+ {
+ "<leader>dD",
+ function()
+ require("devcontainer.commands").remove_all()
+ end,
+ desc = "Remove all (docker)",
+ },
+ {
+ "<leader>dol",
+ function()
+ require("devcontainer.commands").open_logs()
+ end,
+ desc = "Open logs (docker)",
+ },
+ {
+ "<leader>doc",
+ function()
+ require("devcontainer.commands").open_nearest_devcontainer_config()
+ end,
+ desc = "Open nearest config (docker)",
+ },
+ {
+ "<leader>de",
+ function()
+ require("devcontainer.commands").edit_devcontainer_config()
+ end,
+ desc = "Edit nearest config (docker)",
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/git.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/git.lua
new file mode 100644
index 0000000..9f856b6
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/git.lua
@@ -0,0 +1,221 @@
+return {
+ {
+ "moyiz/git-dev.nvim",
+ event = "VeryLazy",
+ opts = {},
+ config = function()
+ require("git-dev").setup()
+
+ local function copy_git_repo_url()
+ -- Get the Git repository root
+ local git_root = vim.fn.system("git rev-parse --show-toplevel"):gsub("\n", "")
+ if git_root == "" then
+ vim.notify("Not inside a Git repository", vim.log.levels.WARN)
+ return nil
+ end
+
+ -- Get the remote URL
+ local remote_url = vim.fn.system("git config --get remote.origin.url"):gsub("\n", "")
+ if remote_url == "" then
+ vim.notify("No remote URL found", vim.log.levels.WARN)
+ return nil
+ end
+
+ -- Convert SSH URL to HTTPS if needed
+ if remote_url:match("^git@") then
+ remote_url = remote_url:gsub("git@", "https://"):gsub(":", "/")
+ end
+
+ -- Remove `.git` from the remote URL
+ remote_url = remote_url:gsub("%.git$", "")
+
+ -- Get the relative path of the current file
+ local file_path = vim.fn.expand("%:~:.")
+ if file_path == "" then
+ vim.notify("No file currently open", vim.log.levels.WARN)
+ return nil
+ end
+
+ -- Get the relative path to the repository root
+ local relative_path =
+ vim.fn.system("git ls-files --full-name " .. vim.fn.shellescape(file_path)):gsub("\n", "")
+
+ -- Combine the remote URL with the relative file path
+ local full_url = remote_url .. "/" .. relative_path
+
+ -- Copy to clipboard
+ vim.fn.setreg("+", full_url)
+ vim.notify("Copied to clipboard: " .. full_url, vim.log.levels.INFO)
+
+ return full_url
+ end
+
+ -- Keybinding to copy the Git repository URL
+ vim.keymap.set("n", "<leader>cg", function()
+ copy_git_repo_url()
+ end, { desc = "Copy git repository URL to clipboard" })
+
+ -- Function to open a repository from the clipboard
+ vim.keymap.set("n", "<leader>er", function()
+ local url = vim.fn.getreg("+") -- Get URL from clipboard
+ if not url or url == "" then
+ vim.notify("Clipboard is empty. Copy a valid URL first.", vim.log.levels.WARN)
+ return
+ end
+ if url:match("^https://") then
+ require("git-dev").open(url)
+ else
+ vim.notify("Not a valid URL: " .. url, vim.log.levels.ERROR)
+ end
+ end, { desc = "Open Git repository from clipboard" })
+ end,
+ },
+ {
+ "lewis6991/gitsigns.nvim",
+ opts = {
+ signs = {
+ add = { text = "▎" },
+ change = { text = "▎" },
+ delete = { text = "" },
+ topdelete = { text = "" },
+ changedelete = { text = "▎" },
+ untracked = { text = "▎" },
+ },
+ signs_staged = {
+ add = { text = "▎" },
+ change = { text = "▎" },
+ delete = { text = "" },
+ topdelete = { text = "" },
+ changedelete = { text = "▎" },
+ },
+ on_attach = function(buffer)
+ local gs = package.loaded.gitsigns
+
+ local function map(mode, l, r, desc)
+ vim.keymap.set(mode, l, r, { buffer = buffer, desc = desc })
+ end
+
+ -- stylua: ignore start
+ map("n", "]h", function()
+ if vim.wo.diff then
+ vim.cmd.normal({ "]c", bang = true })
+ else
+ gs.nav_hunk("next")
+ end
+ end, "Next hunk")
+ map("n", "[h", function()
+ if vim.wo.diff then
+ vim.cmd.normal({ "[c", bang = true })
+ else
+ gs.nav_hunk("prev")
+ end
+ end, "Previous hunk")
+ map("n", "]H", function() gs.nav_hunk("last") end, "Last hunk")
+ map("n", "[H", function() gs.nav_hunk("first") end, "First hunk")
+ map({ "n", "v" }, "<leader>gs", ":Gitsigns stage_hunk<CR>", "Stage hunk")
+ map({ "n", "v" }, "<leader>gr", ":Gitsigns reset_hunk<CR>", "Reset hunk")
+ map("n", "<leader>gS", gs.stage_buffer, "Stage buffer")
+ map("n", "<leader>gu", gs.undo_stage_hunk, "Undo stage hunk")
+ map("n", "<leader>gR", gs.reset_buffer, "Reset buffer")
+ map("n", "<leader>gpv", gs.preview_hunk_inline, "Preview hunk inline")
+ map("n", "<leader>gb", function() gs.blame_line({ full = true }) end, "Blame line")
+ map("n", "<leader>gB", function() gs.blame() end, "Blame buffer")
+ map("n", "<leader>gd", gs.diffthis, "Diff this")
+ map("n", "<leader>gD", function() gs.diffthis("~") end, "Diff this ~")
+ -- map("n", "<leader>gD", function() gs.diffthis("@") end, "Diff this @")
+ map({ "o", "x" }, "ih", ":<C-U>Gitsigns select_hunk<CR>", "GitSigns select hunk")
+ map("n", "<leader>gtb", gs.toggle_current_line_blame, "Toggle line blame")
+ map("n", "<leader>gtd", gs.toggle_deleted, "Toggle delete")
+ end,
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>g", group = "Git" },
+ })
+ end,
+ },
+ {
+ "kdheepak/lazygit.nvim",
+ cmd = {
+ "LazyGit",
+ "LazyGitConfig",
+ "LazyGitCurrentFile",
+ "LazyGitFilter",
+ "LazyGitFilterCurrentFile",
+ },
+ -- optional for floating window border decoration
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ },
+ -- setting the keybinding for LazyGit with 'keys' is recommended in
+ -- order to load the plugin when the command is run for the first time
+ keys = {
+ { "<leader>gg", "<cmd>LazyGit<cr>", desc = "Lazygit" },
+ },
+ },
+ {
+ "mbbill/undotree",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>gt", group = "Toggle" },
+ })
+ end,
+ config = function()
+ vim.keymap.set("n", "<leader>gtu", vim.cmd.UndotreeToggle, { desc = "Toggle undo tree" })
+ end,
+ },
+ {
+ "tpope/vim-fugitive",
+ config = function()
+ local TheSiahxyz_Fugitive = vim.api.nvim_create_augroup("TheSiahxyz_Fugitive", {})
+ local autocmd = vim.api.nvim_create_autocmd
+ autocmd("BufWinEnter", {
+ group = TheSiahxyz_Fugitive,
+ pattern = "*",
+ callback = function()
+ if vim.bo.ft ~= "fugitive" then
+ return
+ end
+
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.keymap.set("n", "<leader>P", function()
+ vim.cmd.Git("push")
+ end, { buffer = bufnr, remap = false, desc = "Git push" })
+ vim.keymap.set("n", "<leader>p", function()
+ vim.cmd.Git({ "pull", "--rebase" })
+ end, { buffer = bufnr, remap = false, desc = "Git pull" })
+ vim.keymap.set(
+ "n",
+ "<leader>o",
+ ":Git push -u origin ",
+ { buffer = bufnr, remap = false, desc = "Git push origin" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>h",
+ ":Git push home ",
+ { buffer = bufnr, remap = false, desc = "Git push home" }
+ )
+ end,
+ })
+ autocmd("FileType", {
+ group = TheSiahxyz_Fugitive,
+ pattern = "fugitive",
+ callback = function(event)
+ vim.bo[event.buf].buflisted = false
+ vim.keymap.set("n", "q", "<cmd>close<cr>", { buffer = event.buf, silent = true })
+ end,
+ })
+ end,
+ keys = {
+ { mode = "n", "<leader>g<leader>", ":Git ", desc = "Git" },
+ { mode = "n", "<leader>gf", vim.cmd.Git, desc = "Git fugitive" },
+ { mode = "n", "gm", "<cmd>diffget //2<cr>", desc = "Git diff on my side" },
+ { mode = "n", "go", "<cmd>diffget //3<cr>", desc = "Git diff on their side" },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/goyo.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/goyo.lua
new file mode 100644
index 0000000..d78fe5e
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/goyo.lua
@@ -0,0 +1,39 @@
+return {
+ "junegunn/goyo.vim",
+ dependencies = "junegunn/seoul256.vim",
+ config = function()
+ -- Enable Goyo by default for mutt writing
+ vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ group = vim.api.nvim_create_augroup("TheSiahxyz_goyo_config", { clear = true }),
+ pattern = "/tmp/neomutt*",
+ callback = function()
+ vim.g.goyo_width = 80
+ vim.g.seoul256_background = 235
+ vim.cmd([[
+ Goyo
+ set bg=light
+ set linebreak
+ set wrap
+ set textwidth=0
+ set wrapmargin=0
+ set background=dark
+ colorscheme seoul256
+ ]])
+ vim.api.nvim_buf_set_keymap(
+ 0,
+ "n",
+ "<leader>gd",
+ ":Goyo|x!<CR>",
+ { noremap = true, silent = true, desc = "Goyo quit" }
+ )
+ vim.api.nvim_buf_set_keymap(
+ 0,
+ "n",
+ "<leader>gq",
+ ":Goyo|q!<CR>",
+ { noremap = true, silent = true, desc = "Goyo abort" }
+ )
+ end,
+ })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/harpoon2.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/harpoon2.lua
new file mode 100644
index 0000000..942f326
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/harpoon2.lua
@@ -0,0 +1,103 @@
+return {
+ "ThePrimeagen/harpoon",
+ branch = "harpoon2",
+ opts = {
+ menu = {
+ width = vim.api.nvim_win_get_width(0) - 4,
+ },
+ settings = {
+ save_on_toggle = true,
+ },
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<leader>h", group = "Harpoon" },
+ { "<leader>hr", group = "Replace harpoon slot" },
+ { "<M-x>", group = "Harpoon list delete" },
+ })
+ end,
+ config = function(_, opts)
+ local harpoon = require("harpoon")
+
+ -- Apply the base configuration
+ harpoon.setup(opts)
+
+ -- Extend functionality
+ harpoon:extend({
+ UI_CREATE = function(cx)
+ vim.keymap.set("n", "<C-v>", function()
+ harpoon.ui:select_menu_item({ vsplit = true })
+ end, { buffer = cx.bufnr })
+
+ vim.keymap.set("n", "<C-s>", function()
+ harpoon.ui:select_menu_item({ split = true })
+ end, { buffer = cx.bufnr })
+
+ vim.keymap.set("n", "<C-t>", function()
+ harpoon.ui:select_menu_item({ tabedit = true })
+ end, { buffer = cx.bufnr })
+ end,
+ })
+ end,
+ keys = function()
+ local keys = {
+ {
+ "<leader>ha",
+ function()
+ require("harpoon"):list():add()
+ end,
+ desc = "Add buffer to harpoon list",
+ },
+ {
+ "<C-q>",
+ function()
+ local harpoon = require("harpoon")
+ harpoon.ui:toggle_quick_menu(harpoon:list())
+ end,
+ desc = "Open harpoon list menu",
+ },
+ {
+ "<C-S-P>",
+ function()
+ require("harpoon"):list():prev({ ui_nav_wrap = false })
+ end,
+ desc = "Previous harpoon list",
+ },
+ {
+ "<C-S-N>",
+ function()
+ require("harpoon"):list():next({ ui_nav_wrap = false })
+ end,
+ desc = "Next harpoon list",
+ },
+ }
+
+ for i = 1, 5 do
+ table.insert(keys, {
+ "<M-" .. i .. ">",
+ function()
+ require("harpoon"):list():select(i)
+ end,
+ desc = "Harpoon list " .. i,
+ })
+ table.insert(keys, {
+ "<leader>h" .. i,
+ function()
+ require("harpoon"):list():select(i)
+ end,
+ desc = "Harpoon list " .. i,
+ })
+ table.insert(keys, {
+ "<leader>hr" .. i,
+ function()
+ require("harpoon"):list():replace_at(i)
+ end,
+ desc = "Replace buffer at harpoon slot " .. i,
+ })
+ end
+
+ return keys
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/image.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/image.lua
new file mode 100644
index 0000000..a117e28
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/image.lua
@@ -0,0 +1,210 @@
+return {
+ "HakonHarnes/img-clip.nvim",
+ event = "VeryLazy",
+ opts = {
+ -- add options here
+ -- or leave it empty to use the default settings
+ },
+ config = function(_, opts)
+ require("img-clip").setup(opts)
+
+ vim.keymap.set({ "n", "v", "i" }, "<M-i>", function()
+ local pasted_image = require("img-clip").paste_image()
+ if pasted_image then
+ -- "Update" saves only if the buffer has been modified since the last save
+ vim.cmd("silent! update")
+ -- Get the current line
+ local line = vim.api.nvim_get_current_line()
+ -- Move cursor to end of line
+ vim.api.nvim_win_set_cursor(0, { vim.api.nvim_win_get_cursor(0)[1], #line })
+ -- I reload the file, otherwise I cannot view the image after pasted
+ vim.cmd("edit!")
+ end
+ end, { desc = "Paste image from clipboard" })
+
+ vim.keymap.set("n", "<leader>oi", function()
+ local function get_image_path()
+ -- Get the current line
+ local line = vim.api.nvim_get_current_line()
+ -- Patterns to match image paths
+ local patterns = {
+ "%[.-%]%((.-)%)", -- Markdown style: ![alt text](image_path)
+ "%[%[(.-)%]%]", -- Double square brackets: [[image_path]]
+ }
+
+ for _, pattern in ipairs(patterns) do
+ local _, _, image_path = string.find(line, pattern)
+ if image_path then
+ return image_path
+ end
+ end
+
+ return nil -- Return nil if no pattern matches
+ end
+
+ local function file_exists(filepath)
+ -- Check if the file exists
+ local f = io.open(filepath, "r")
+ if f then
+ f:close()
+ return true
+ end
+ return false
+ end
+
+ -- Get the image path
+ local image_path = get_image_path()
+ if image_path then
+ -- Check if the image path starts with "http" or "https"
+ if string.sub(image_path, 1, 4) == "http" then
+ print("URL image, use 'gx' to open it in the default browser.")
+ else
+ -- Construct absolute image path
+ local current_file_path = vim.fn.expand("%:p:h")
+ local absolute_image_path = current_file_path .. "/" .. image_path
+ -- Check if the image exists in the current path
+ if not file_exists(absolute_image_path) then
+ -- If not found, search ../Screenshots/
+ local fallback_path = vim.fn.fnamemodify(current_file_path, ":h")
+ .. "/Screenshots/"
+ .. image_path
+ if file_exists(fallback_path) then
+ absolute_image_path = fallback_path
+ else
+ print("Image not found in either current directory or ../Screenshots/")
+ return
+ end
+ end
+ -- Construct command to open image in Preview
+ local command = "nsxiv -aiop " .. vim.fn.shellescape(absolute_image_path)
+ -- Execute the command
+ local success = os.execute(command)
+ if success then
+ print("Opened image: " .. absolute_image_path)
+ else
+ print("Failed to open image: " .. absolute_image_path)
+ end
+ end
+ else
+ print("No image found under the cursor")
+ end
+ end, { desc = "Open image under cursor" })
+
+ vim.keymap.set("n", "<leader>di", function()
+ local function get_image_path()
+ local line = vim.api.nvim_get_current_line()
+ -- Patterns to match image paths
+ local patterns = {
+ "%[.-%]%((.-)%)", -- Markdown style: ![alt text](image_path)
+ "%[%[(.-)%]%]", -- Double square brackets: [[image_path]]
+ }
+
+ for _, pattern in ipairs(patterns) do
+ local _, _, image_path = string.find(line, pattern)
+ if image_path then
+ return image_path
+ end
+ end
+
+ return nil -- Return nil if no pattern matches
+ end
+
+ local function file_exists(filepath)
+ local f = io.open(filepath, "r")
+ if f then
+ f:close()
+ return true
+ end
+ return false
+ end
+
+ local image_path = get_image_path()
+ if image_path then
+ if string.sub(image_path, 1, 4) == "http" then
+ vim.api.nvim_echo({ { "URL image cannot be deleted from disk.", "WarningMsg" } }, false, {})
+ return
+ end
+
+ local current_file_path = vim.fn.expand("%:p:h")
+ local absolute_image_path = current_file_path .. "/" .. image_path
+
+ -- Check if file exists
+ if not file_exists(absolute_image_path) then
+ -- Search fallback directory
+ local fallback_path = vim.fn.fnamemodify(current_file_path, ":h") .. "/Screenshots/" .. image_path
+ if file_exists(fallback_path) then
+ absolute_image_path = fallback_path
+ else
+ print("Image not found in either current directory or ../Screenshots/")
+ return
+ end
+ end
+
+ -- Verify if trash utility exists
+ if vim.fn.executable("trash") == 0 then
+ vim.api.nvim_echo({
+ { "- Trash utility not installed. Install `trash-cli`.\n", "ErrorMsg" },
+ }, false, {})
+ return
+ end
+
+ vim.ui.select({ "yes", "no" }, { prompt = "Delete image file? " }, function(choice)
+ if choice == "yes" then
+ -- Attempt to delete using trash
+ local command = { "trash", absolute_image_path }
+ local success, err = pcall(vim.fn.system, command)
+
+ -- Debug message for troubleshooting
+ print("Debug: Trash command -", table.concat(command, " "))
+
+ if success and vim.fn.filereadable(absolute_image_path) == 0 then
+ vim.api.nvim_echo({
+ { "Image file deleted using trash:\n", "Normal" },
+ { absolute_image_path, "Normal" },
+ }, false, {})
+ require("image").clear()
+ vim.cmd("edit!")
+ vim.cmd("normal! dd")
+ else
+ -- If trash fails, log the error
+ vim.api.nvim_echo({
+ { "Trash deletion failed. Error:\n", "ErrorMsg" },
+ { err or "Unknown error", "ErrorMsg" },
+ }, false, {})
+ end
+ else
+ vim.api.nvim_echo({ { "Image deletion canceled.", "Normal" } }, false, {})
+ end
+ end)
+ else
+ print("No image found under the cursor")
+ end
+ end, { desc = "Delete image file under cursor" })
+
+ -- Refresh the images in the current buffer
+ -- Useful if you delete an actual image file and want to see the changes
+ -- without having to re-open neovim
+ vim.keymap.set("n", "<leader>ir", function()
+ -- First I clear the images
+ require("image").clear()
+ -- I'm using [[ ]] to escape the special characters in a command
+ -- vim.cmd([[lua require("image").clear()]])
+ -- Reloads the file to reflect the changes
+ vim.cmd("edit!")
+ print("Images refreshed")
+ end, { desc = "Refresh images" })
+
+ -- Set up a keymap to clear all images in the current buffer
+ vim.keymap.set("n", "<leader>ic", function()
+ -- This is the command that clears the images
+ require("image").clear()
+ -- I'm using [[ ]] to escape the special characters in a command
+ -- vim.cmd([[lua require("image").clear()]])
+ print("Images cleared")
+ end, { desc = "Clear images" })
+ end,
+ keys = {
+ -- suggested keymap
+ { "<leader>pi", "<cmd>PasteImage<cr>", desc = "Paste image from clipboard" },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/init.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/init.lua
new file mode 100644
index 0000000..0bcf162
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/init.lua
@@ -0,0 +1,83 @@
+return {
+ { "nvim-lua/plenary.nvim" },
+ {
+ "aserowy/tmux.nvim",
+ config = function()
+ require("tmux").setup({
+ copy_sync = {
+ -- enables copy sync. by default, all registers are synchronized.
+ -- to control which registers are synced, see the `sync_*` options.
+ enable = false,
+
+ -- ignore specific tmux buffers e.g. buffer0 = true to ignore the
+ -- first buffer or named_buffer_name = true to ignore a named tmux
+ -- buffer with name named_buffer_name :)
+ ignore_buffers = { empty = false },
+
+ -- TMUX >= 3.2: all yanks (and deletes) will get redirected to system
+ -- clipboard by tmux
+ redirect_to_clipboard = false,
+
+ -- offset controls where register sync starts
+ -- e.g. offset 2 lets registers 0 and 1 untouched
+ register_offset = 0,
+
+ -- overwrites vim.g.clipboard to redirect * and + to the system
+ -- clipboard using tmux. If you sync your system clipboard without tmux,
+ -- disable this option!
+ sync_clipboard = true,
+
+ -- synchronizes registers *, +, unnamed, and 0 till 9 with tmux buffers.
+ sync_registers = true,
+
+ -- synchronizes registers when pressing p and P.
+ sync_registers_keymap_put = true,
+
+ -- synchronizes registers when pressing (C-r) and ".
+ sync_registers_keymap_reg = true,
+
+ -- syncs deletes with tmux clipboard as well, it is adviced to
+ -- do so. Nvim does not allow syncing registers 0 and 1 without
+ -- overwriting the unnamed register. Thus, ddp would not be possible.
+ sync_deletes = true,
+
+ -- syncs the unnamed register with the first buffer entry from tmux.
+ sync_unnamed = true,
+ },
+ navigation = {
+ -- cycles to opposite pane while navigating into the border
+ cycle_navigation = false,
+
+ -- enables default keybindings (C-hjkl) for normal mode
+ enable_default_keybindings = true,
+
+ -- prevents unzoom tmux when navigating beyond vim border
+ persist_zoom = true,
+ },
+ resize = {
+ -- enables default keybindings (A-hjkl) for normal mode
+ enable_default_keybindings = false,
+
+ -- sets resize steps for x axis
+ resize_step_x = 2,
+
+ -- sets resize steps for y axis
+ resize_step_y = 2,
+ },
+ })
+
+ vim.keymap.set("n", "<C-left>", function()
+ require("tmux").resize_left()
+ end, { desc = "Decrease window width" })
+ vim.keymap.set("n", "<C-down>", function()
+ require("tmux").resize_bottom()
+ end, { desc = "Decrease window height" })
+ vim.keymap.set("n", "<C-up>", function()
+ require("tmux").resize_top()
+ end, { desc = "Increase window height" })
+ vim.keymap.set("n", "<C-right>", function()
+ require("tmux").resize_right()
+ end, { desc = "Increase window width" })
+ end,
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/keys.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/keys.lua
new file mode 100644
index 0000000..5927c53
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/keys.lua
@@ -0,0 +1,278 @@
+return {
+ {
+ "nvzone/showkeys",
+ cmd = "ShowkeysToggle",
+ opts = {},
+ keys = {
+ { "<leader>zk", "<cmd>ShowkeysToggle<cr>", desc = "Toggle keys" },
+ },
+ },
+ {
+ "folke/which-key.nvim",
+ event = "VeryLazy",
+ cmd = "WhichKey",
+ dependencies = { "echasnovski/mini.icons", "nvim-tree/nvim-web-devicons" },
+ opts = {},
+ config = function()
+ local wk = require("which-key")
+ wk.setup({
+ ---@type false | "classic" | "modern" | "helix"
+ preset = "classic",
+ -- Delay before showing the popup. Can be a number or a function that returns a number.
+ ---@type number | fun(ctx: { keys: string, mode: string, plugin?: string }):number
+ delay = function(ctx)
+ return ctx.plugin and 0 or 200
+ end,
+ ---@param mapping wk.Mapping
+ filter = function(mapping)
+ -- example to exclude mappings without a description
+ -- return mapping.desc and mapping.desc ~= ""
+ return true
+ end,
+ --- You can add any mappings here, or use `require('which-key').add()` later
+ ---@type wk.Spec
+ spec = {},
+ -- show a warning when issues were detected with your mappings
+ notify = true,
+ -- Which-key automatically sets up triggers for your mappings.
+ -- But you can disable this and setup the triggers manually.
+ -- Check the docs for more info.
+ ---@type wk.Spec
+ triggers = {
+ { "<auto>", mode = "nxso" },
+ },
+ -- Start hidden and wait for a key to be pressed before showing the popup
+ -- Only used by enabled xo mapping modes.
+ ---@param ctx { mode: string, operator: string }
+ defer = function(ctx)
+ return ctx.mode == "V" or ctx.mode == "<C-V>"
+ end,
+ plugins = {
+ marks = true, -- shows a list of your marks on ' and `
+ registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
+ -- the presets plugin, adds help for a bunch of default keybindings in Neovim
+ -- No actual key bindings are created
+ spelling = {
+ enabled = true, -- enabling this will show WhichKey when pressing z= to select spelling suggestions
+ suggestions = 20, -- how many suggestions should be shown in the list?
+ },
+ presets = {
+ operators = true, -- adds help for operators like d, y, ...
+ motions = true, -- adds help for motions
+ text_objects = true, -- help for text objects triggered after entering an operator
+ windows = true, -- default bindings on <c-w>
+ nav = true, -- misc bindings to work with windows
+ z = true, -- bindings for folds, spelling and others prefixed with z
+ g = true, -- bindings for prefixed with g
+ },
+ },
+ ---@type wk.Win.opts
+ win = {
+ -- don't allow the popup to overlap with the cursor
+ no_overlap = true,
+ -- width = 1,
+ -- height = { min = 4, max = 25 },
+ -- col = 0,
+ -- row = math.huge,
+ -- border = "none",
+ padding = { 1, 2 }, -- extra window padding [top/bottom, right/left]
+ title = true,
+ title_pos = "center",
+ zindex = 1000,
+ -- Additional vim.wo and vim.bo options
+ bo = {},
+ wo = {
+ -- winblend = 10, -- value between 0-100 0 for fully opaque and 100 for fully transparent
+ },
+ },
+ layout = {
+ width = { min = 20 }, -- min and max width of the columns
+ spacing = 3, -- spacing between columns
+ },
+ keys = {
+ scroll_down = "<c-e>",
+ scroll_up = "<c-y>",
+ },
+ ---@type (string|wk.Sorter)[]
+ --- Mappings are sorted using configured sorters and natural sort of the keys
+ --- Available sorters:
+ --- * local: buffer-local mappings first
+ --- * order: order of the items (Used by plugins like marks / registers)
+ --- * group: groups last
+ --- * alphanum: alpha-numerical first
+ --- * mod: special modifier keys last
+ --- * manual: the order the mappings were added
+ --- * case: lower-case first
+ sort = { "local", "order", "group", "alphanum", "mod" },
+ ---@type number|fun(node: wk.Node):boolean?
+ expand = 0, -- expand groups when <= n mappings
+ -- expand = function(node)
+ -- return not node.desc -- expand all nodes without a description
+ -- end,
+ -- Functions/Lua Patterns for formatting the labels
+ ---@type table<string, ({[1]:string, [2]:string}|fun(str:string):string)[]>
+ replace = {
+ key = {
+ function(key)
+ return require("which-key.view").format(key)
+ end,
+ -- { "<Space>", "SPC" },
+ },
+ desc = {
+ { "<Plug>%(?(.*)%)?", "%1" },
+ { "^%+", "" },
+ { "<[cC]md>", "" },
+ { "<[cC][rR]>", "" },
+ { "<[sS]ilent>", "" },
+ { "^lua%s+", "" },
+ { "^call%s+", "" },
+ { "^:%s*", "" },
+ },
+ },
+ icons = {
+ breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
+ separator = "➜", -- symbol used between a key and it's label
+ group = "+", -- symbol prepended to a group
+ ellipsis = "…",
+ -- set to false to disable all mapping icons,
+ -- both those explicitly added in a mapping
+ -- and those from rules
+ mappings = true,
+ --- See `lua/which-key/icons.lua` for more details
+ --- Set to `false` to disable keymap icons from rules
+ ---@type wk.IconRule[]|false
+ rules = {},
+ -- use the highlights from mini.icons
+ -- When `false`, it will use `WhichKeyIcon` instead
+ colors = true,
+ -- used by key format
+ keys = {
+ Up = " ",
+ Down = " ",
+ Left = " ",
+ Right = " ",
+ C = "󰘴 ",
+ M = "󰘵 ",
+ D = "󰘳 ",
+ S = "󰘶 ",
+ CR = "󰌑 ",
+ Esc = "󱊷 ",
+ ScrollWheelDown = "󱕐 ",
+ ScrollWheelUp = "󱕑 ",
+ NL = "󰌑 ",
+ BS = "󰁮",
+ Space = "󱁐 ",
+ Tab = "󰌒 ",
+ F1 = "󱊫",
+ F2 = "󱊬",
+ F3 = "󱊭",
+ F4 = "󱊮",
+ F5 = "󱊯",
+ F6 = "󱊰",
+ F7 = "󱊱",
+ F8 = "󱊲",
+ F9 = "󱊳",
+ F10 = "󱊴",
+ F11 = "󱊵",
+ F12 = "󱊶",
+ },
+ },
+ show_help = true, -- show a help message in the command line for using WhichKey
+ show_keys = true, -- show the currently pressed key and its label as a message in the command line
+ -- disable WhichKey for certain buf types and file types.
+ disable = {
+ ft = {},
+ bt = {},
+ },
+ debug = false, -- enable wk.log in the current directory
+ })
+
+ wk.add({
+ {
+ mode = { "n", "v" },
+ { "g", group = "Goto" },
+ { "g`", group = "Marks" },
+ { "g'", group = "Marks" },
+ { "gs", group = "Search/Surround" },
+ { "s", group = "Surround/Search & replace on line" },
+ { "S", group = "Surround/Search & replace in file" },
+ { "z", group = "Fold" },
+ { "`", group = "Marks" },
+ { "'", group = "Marks" },
+ { '"', group = "Registers" },
+ { "]", group = "Next" },
+ { "]]", group = "Next" },
+ { "[", group = "Prev" },
+ { "[[", group = "Prev" },
+ { "=", group = "Line paste" },
+ { "gx", desc = "Open with system app" },
+ { "<C-w>", group = "Windows" },
+ { "<leader>", group = "Leader" },
+ { "<leader>a", group = "Ascii" },
+ {
+ "<leader>b",
+ group = "Buffer",
+ expand = function()
+ return require("which-key.extras").expand.buf()
+ end,
+ },
+ { "<leader>B", group = "Buffer (force)" },
+ { "<leader>C", group = "Goto realpath" },
+ { "<leader>d", group = "Delete" },
+ { "<leader>D", group = "Delete (blackhole)" },
+ { "<leader>e", group = "Explorer" },
+ { "<leader>i", group = "Inspect" },
+ { "<leader>l", group = "Location" },
+ { "<leader>L", group = "Lazy" },
+ { "<leader>M", group = "Mason" },
+ { "<leader>o", group = "Open" },
+ { "<leader>p", group = "Paste" },
+ { "<leader>P", group = "Paste" },
+ { "<leader>q", group = "Quit" },
+ { "<leader>sk", group = "Keys" },
+ { "<leader>S", group = "Save/Source" },
+ { "<leader>w", group = "Which-key" },
+ { "<leader>W", group = "Save all" },
+ { "<leader>z", group = "Toggle" },
+ { "<leader><tab>", group = "Tabs" },
+ { "<localleader>", group = "Local Leader (bookmarks)" },
+ { "<localleader><leader>", group = "Bookmarks (explorer)" },
+ { "<localleader><localleader>", group = "Bookmarks (mini)" },
+ { "<localleader>t", group = "Task" },
+ },
+ {
+ mode = { "n", "v", "x" },
+ { "gw", desc = "Visible in window" },
+ { "g%", desc = "Match backward" },
+ { "g;", desc = "Last change" },
+ { "<leader>Q", group = "Quit all" },
+ },
+ })
+ end,
+ keys = {
+ {
+ "<leader>?",
+ function()
+ require("which-key").show({ global = false })
+ end,
+ desc = "Buffer local keymaps (which-key)",
+ },
+ {
+ "<leader>wk",
+ function()
+ local ok, input = pcall(vim.fn.input, "WhichKey: ")
+ if ok and input ~= "" then
+ vim.cmd("WhichKey " .. input)
+ end
+ end,
+ desc = "Which-key query lookup",
+ },
+ {
+ mode = { "n", "v", "x" },
+ "<leader>wK",
+ "<cmd>WhichKey<cr>",
+ desc = "Which-key all key",
+ },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lf.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lf.lua
new file mode 100644
index 0000000..b055c6b
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lf.lua
@@ -0,0 +1,121 @@
+return {
+ "lmburns/lf.nvim",
+ dependencies = {
+ {
+ "akinsho/toggleterm.nvim",
+ version = "*",
+ config = function()
+ require("toggleterm").setup({
+ open_mapping = [[<leader><c-s>]], -- or { [[<c-\>]], [[<c-¥>]] } if you also use a Japanese keyboard.
+ })
+ vim.keymap.set(
+ "n",
+ "<leader><C-\\>",
+ "<cmd>ToggleTerm direction=float name=Terminal<cr>",
+ { desc = "Toggle float terminal" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader><C-t>",
+ "<cmd>ToggleTermToggleAll<cr>",
+ { desc = "Toggle all float terminals" }
+ )
+ vim.keymap.set("n", "<leader><C-u>", "<cmd>TermSelect<cr>", { desc = "Select float terminal" })
+
+ local function set_opfunc(opfunc)
+ _G._opfunc = opfunc -- Define the function globally
+ vim.go.operatorfunc = "v:lua._opfunc" -- Assign the global function
+ end
+
+ local trim_spaces = false
+ vim.keymap.set("v", "<leader><C-l>", function()
+ require("toggleterm").send_lines_to_terminal("single_line", trim_spaces, { args = vim.v.count })
+ end, { desc = "Send line to terminal" })
+ -- Replace with these for the other two options
+ -- require("toggleterm").send_lines_to_terminal("visual_lines", trim_spaces, { args = vim.v.count })
+ -- require("toggleterm").send_lines_to_terminal("visual_selection", trim_spaces, { args = vim.v.count })
+
+ -- For use as an operator map:
+ -- Send motion to terminal
+ vim.keymap.set("n", "<leader><C-l>", function()
+ set_opfunc(function(motion_type)
+ require("toggleterm").send_lines_to_terminal(motion_type, false, { args = vim.v.count })
+ end)
+ vim.api.nvim_feedkeys("g@", "n", false)
+ end, { desc = "Send motions to terminal" })
+ -- Double the command to send line to terminal
+ vim.keymap.set("n", "<leader><C-a>", function()
+ set_opfunc(function(motion_type)
+ require("toggleterm").send_lines_to_terminal(motion_type, false, { args = vim.v.count })
+ end)
+ vim.api.nvim_feedkeys("g@_", "n", false)
+ end, { desc = "Send double command to terminal" })
+ -- Send whole file
+ vim.keymap.set("n", "<leader><C-g>", function()
+ set_opfunc(function(motion_type)
+ require("toggleterm").send_lines_to_terminal(motion_type, false, { args = vim.v.count })
+ end)
+ vim.api.nvim_feedkeys("ggg@G''", "n", false)
+ end, { desc = "Send whole file to terminal (clipboard)" })
+ end,
+ },
+ },
+ config = function()
+ vim.g.lf_netrw = 1
+ local fn = vim.fn
+
+ -- Defaults
+ require("lf").setup({
+ default_action = "drop", -- Default action when `Lf` opens a file
+ default_actions = {
+ ["e"] = "tabedit",
+ ["<C-t>"] = "tab drop",
+ ["<C-v>"] = "vsplit",
+ ["<C-x>"] = "split",
+ },
+ winblend = 0, -- Pseudotransparency level
+ direction = "float", -- Window type
+ border = "rounded", -- Border kind
+ height = fn.float2nr(fn.round(0.75 * vim.o.lines)), -- Height of the *floating* window
+ width = fn.float2nr(fn.round(0.75 * vim.o.columns)), -- Width of the *floating* window
+ escape_quit = true, -- Map escape to the quit command
+ focus_on_open = true, -- Focus the current file when opening Lf
+ mappings = true, -- Enable terminal buffer mapping
+ tmux = true, -- Tmux statusline can be disabled
+ disable_netrw_warning = true, -- Don't display a message when opening a directory
+ highlights = {
+ Normal = { link = "Normal" }, -- Use normal highlighting
+ NormalFloat = { link = "NormalFloat" }, -- Use float highlighting
+ FloatBorder = { link = "@constant" }, -- Use constant highlighting
+ },
+
+ -- Layout configurations
+ layout_mapping = "<M-r>", -- Resize window with this key
+ views = {
+ { width = 0.800, height = 0.800 },
+ { width = 0.600, height = 0.600 },
+ { width = 0.950, height = 0.950 },
+ { width = 0.500, height = 0.500, col = 0, row = 0 },
+ { width = 0.500, height = 0.500, col = 0, row = 0.5 },
+ { width = 0.500, height = 0.500, col = 0.5, row = 0 },
+ { width = 0.500, height = 0.500, col = 0.5, row = 0.5 },
+ },
+ })
+
+ vim.keymap.set("n", "<leader>el", "<Cmd>Lf<CR>")
+
+ -- Autocommand to set key mapping in terminal buffer
+ vim.api.nvim_create_autocmd("User", {
+ pattern = "LfTermEnter",
+ callback = function(a)
+ vim.api.nvim_buf_set_keymap(
+ a.buf,
+ "t",
+ "q",
+ "<cmd>q<CR>",
+ { nowait = true, noremap = true, silent = true }
+ )
+ end,
+ })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lsp.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lsp.lua
new file mode 100644
index 0000000..16b95fe
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lsp.lua
@@ -0,0 +1,643 @@
+return {
+ {
+ "neovim/nvim-lspconfig",
+ event = { "BufReadPre", "BufNewFile" },
+ dependencies = {
+ "williamboman/mason.nvim",
+ "williamboman/mason-lspconfig.nvim",
+ "WhoIsSethDaniel/mason-tool-installer.nvim",
+ "hrsh7th/cmp-nvim-lsp",
+ "hrsh7th/cmp-buffer",
+ "hrsh7th/cmp-path",
+ "hrsh7th/cmp-cmdline",
+ "hrsh7th/nvim-cmp",
+ {
+ "L3MON4D3/LuaSnip",
+ version = "v2.*",
+ build = "make install_jsregexp",
+ },
+ "mfussenegger/nvim-lint",
+ "saadparwaiz1/cmp_luasnip",
+ "j-hui/fidget.nvim",
+ { "folke/neoconf.nvim", cmd = "Neoconf", config = false, dependencies = { "nvim-lspconfig" } },
+ {
+ "folke/lazydev.nvim",
+ ft = "lua", -- only load on lua files
+ opts = {
+ library = {
+ -- See the configuration section for more details
+ -- Load luvit types when the `vim.uv` word is found
+ { path = "${3rd}/luv/library", words = { "vim%.uv" } },
+ },
+ },
+ },
+ "stevearc/conform.nvim",
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>tf", group = "Format" },
+ })
+ end,
+ config = function()
+ local cmp = require("cmp")
+ local cmp_lsp = require("cmp_nvim_lsp")
+ local capabilities = vim.tbl_deep_extend(
+ "force",
+ {},
+ vim.lsp.protocol.make_client_capabilities(),
+ cmp_lsp.default_capabilities()
+ )
+ local lspconfig = require("lspconfig")
+
+ require("fidget").setup({
+ progress = {
+ poll_rate = false, -- How and when to poll for progress messages
+ suppress_on_insert = true, -- Suppress new messages while in insert mode
+ ignore_done_already = true, -- Ignore new tasks that are already complete
+ ignore_empty_message = true, -- Ignore new tasks that don't contain a message
+ clear_on_detach = function(client_id) -- Clear notification group when LSP server detaches
+ local client = vim.lsp.get_client_by_id(client_id)
+ return client and client.name or nil
+ end,
+ -- ignore = { "lua_ls" },
+ },
+ notification = {
+ window = {
+ normal_hl = "Comment", -- Base highlight group in the notification window
+ winblend = 0, -- Background color opacity in the notification window
+ border = "none", -- Border around the notification window
+ zindex = 45, -- Stacking priority of the notification window
+ max_width = 0, -- Maximum width of the notification window
+ max_height = 0, -- Maximum height of the notification window
+ x_padding = 1, -- Padding from right edge of window boundary
+ y_padding = 0, -- Padding from bottom edge of window boundary
+ align = "bottom", -- How to align the notification window
+ relative = "editor", -- What the notification window position is relative to
+ },
+ },
+ integration = {
+ ["nvim-tree"] = {
+ enable = false, -- Integrate with nvim-tree/nvim-tree.lua (if installed)
+ },
+ },
+ })
+
+ require("mason").setup()
+ require("mason-lspconfig").setup({
+ ensure_installed = {
+ "dockerls",
+ "docker_compose_language_service",
+ "jdtls",
+ "jsonls",
+ "lua_ls",
+ "pyright",
+ "ruff",
+ },
+ automatic_installation = true,
+ handlers = {
+ function(server_name) -- default handler (optional)
+ require("lspconfig")[server_name].setup({
+ capabilities = capabilities,
+ })
+ end,
+ ["dockerls"] = function()
+ lspconfig.dockerls.setup({
+ capabilities = capabilities,
+ -- settings = {
+ -- python = {
+ -- disableLanguageServices = false,
+ -- disableOrganizeImports = false,
+ -- },
+ -- },
+ })
+ end,
+ ["docker_compose_language_service"] = function()
+ lspconfig.docker_compose_language_service.setup({
+ capabilities = capabilities,
+ -- settings = {
+ -- python = {
+ -- disableLanguageServices = false,
+ -- disableOrganizeImports = false,
+ -- },
+ -- },
+ })
+ end,
+ ["lua_ls"] = function()
+ lspconfig.lua_ls.setup({
+ capabilities = capabilities,
+ settings = {
+ Lua = {
+ runtime = { version = "Lua 5.4" },
+ diagnostics = {
+ globals = { "bit", "vim", "it", "describe", "before_each", "after_each" },
+ },
+ },
+ },
+ })
+ end,
+ ["pyright"] = function()
+ lspconfig.pyright.setup({
+ capabilities = capabilities,
+ settings = {
+ python = {
+ disableLanguageServices = false,
+ disableOrganizeImports = false,
+ },
+ },
+ })
+ end,
+ ["ruff"] = function()
+ lspconfig.ruff.setup({
+ capabilities = capabilities,
+ -- settings = {
+ -- python = {
+ -- disableLanguageServices = false,
+ -- disableOrganizeImports = false,
+ -- },
+ -- },
+ })
+ end,
+ ["jdtls"] = function()
+ lspconfig.jdtls.setup({
+ capabilities = capabilities,
+ })
+ end,
+ ["jsonls"] = function()
+ lspconfig.jsonls.setup({
+ capabilities = capabilities,
+ settings = {
+ json = {
+ format = {
+ enable = true,
+ },
+ validate = { enable = true },
+ },
+ },
+ })
+ end,
+ },
+ })
+
+ local lint = require("lint")
+ lint.linters_by_ft = {
+ dockerfile = { "hadolint" },
+ javascript = { "eslint_d" },
+ typescript = { "eslint_d" },
+ javascriptreact = { "eslint_d" },
+ typescriptreact = { "eslint_d" },
+ svelte = { "eslint_d" },
+ python = { "pylint" },
+ sh = { "shellcheck" },
+ }
+
+ local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true })
+ vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave", "TextChanged" }, {
+ group = lint_augroup,
+ callback = function()
+ lint.try_lint()
+ end,
+ })
+
+ require("mason-tool-installer").setup({
+ ensure_installed = {
+ "beautysh", -- zsh formatter
+ "black", -- python formatter
+ "debugpy", -- python debuger
+ "eslint_d", -- eslint linter
+ "hadolint", -- docker linter
+ "isort", -- python formatter
+ "java-debug-adapter", -- java debugger
+ "java-test", -- java test
+ "markdown-toc", -- markdown toc
+ "prettier", -- prettier formatter
+ "pylint", -- python linter
+ "ruff", -- python formatter
+ "shellcheck", -- bash lint
+ "shfmt", -- sh formatter
+ "stylua", -- lua formatter
+ },
+ integrations = {
+ ["mason-lspconfig"] = true,
+ ["mason-null-ls"] = false,
+ ["mason-nvim-dap"] = true,
+ },
+ })
+
+ local cmp_select = { behavior = cmp.SelectBehavior.Select }
+ local luasnip = require("luasnip")
+
+ cmp.setup({
+ snippet = {
+ expand = function(args)
+ luasnip.lsp_expand(args.body) -- For `luasnip` users.
+ end,
+ },
+ mapping = cmp.mapping.preset.insert({
+ ["<C-u>"] = cmp.mapping.scroll_docs(-4), -- Up
+ ["<C-d>"] = cmp.mapping.scroll_docs(4), -- Down
+ ["<C-p>"] = cmp.mapping.select_prev_item(cmp_select),
+ ["<C-n>"] = cmp.mapping.select_next_item(cmp_select),
+ ["<CR>"] = cmp.mapping.confirm({
+ behavior = cmp.ConfirmBehavior.Replace,
+ select = true,
+ }),
+ ["<C-Space>"] = cmp.mapping.complete(),
+ ["<Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ elseif luasnip.expand_or_jumpable() then
+ luasnip.expand_or_jump()
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end, { "i", "s" }),
+ }),
+ sources = cmp.config.sources({
+ { name = "nvim_lsp" },
+ { name = "luasnip" }, -- For luasnip users.
+ { name = "buffer" },
+ }),
+ })
+
+ vim.diagnostic.config({
+ -- update_in_insert = true,
+ float = {
+ header = "",
+ border = "rounded",
+ prefix = "",
+ source = "if_many",
+ },
+ })
+
+ require("conform").setup({
+ formatters_by_ft = {
+ bash = { "shfmt" },
+ css = { "prettier" },
+ graphql = { "prettier" },
+ html = { "prettier" },
+ javascript = { "prettier" },
+ javascriptreact = { "prettier" },
+ json = { "prettier" },
+ liquid = { "prettier" },
+ lua = { "stylua" },
+ markdown = { "prettier" },
+ python = { "ruff", "isort", "black" },
+ sh = { "shfmt" },
+ svelte = { "prettier" },
+ typescript = { "prettier" },
+ typescriptreact = { "prettier" },
+ yaml = { "prettier" },
+ zsh = { "beautysh" },
+ },
+ default_format_opts = {},
+ format_on_save = function(bufnr)
+ -- Disable with a global or buffer-local variable
+ if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
+ return
+ end
+ return { lsp_format = "fallback", timeout_ms = 1000, async = false }
+ end,
+ })
+
+ vim.api.nvim_create_user_command("FormatDisable", function(args)
+ if args.bang then
+ vim.b.disable_autoformat = true
+ else
+ vim.g.disable_autoformat = true
+ end
+ end, {
+ desc = "Disable autoformat-on-save",
+ bang = true,
+ })
+ vim.api.nvim_create_user_command("FormatEnable", function()
+ vim.b.disable_autoformat = false
+ vim.g.disable_autoformat = false
+ end, {
+ desc = "Re-enable autoformat-on-save",
+ })
+ end,
+ keys = {
+ {
+ mode = { "n", "v" },
+ "<leader>lf",
+ function()
+ require("conform").format({ async = true })
+ end,
+ desc = "Format buffer by lsp",
+ },
+ {
+ "<leader>ci",
+ "<cmd>PyrightOrganizeImports<cr>",
+ desc = "Organize imports",
+ },
+ {
+ "<leader>bl",
+ function()
+ require("lint").try_lint()
+ end,
+ desc = "Buffer lint",
+ },
+ {
+ "<leader>le",
+ "<cmd>FormatEnable<CR>",
+ desc = "Enable format",
+ },
+ {
+ "<leader>ld",
+ "<cmd>FormatDisable<CR>",
+ desc = "Disable format",
+ },
+ {
+ "<leader>lD",
+ "<cmd>FormatDisable!<CR>",
+ desc = "Disable current buffer format",
+ },
+ },
+ },
+ -- {
+ -- "neovim/nvim-lspconfig",
+ -- event = { "BufReadPre", "BufNewFile" },
+ -- dependencies = {
+ -- "williamboman/mason.nvim",
+ -- "williamboman/mason-lspconfig.nvim",
+ -- "WhoIsSethDaniel/mason-tool-installer.nvim",
+ -- "hrsh7th/cmp-nvim-lsp",
+ -- "hrsh7th/cmp-buffer",
+ -- "hrsh7th/cmp-path",
+ -- "hrsh7th/cmp-cmdline",
+ -- {
+ -- "L3MON4D3/LuaSnip",
+ -- version = "v2.*",
+ -- build = "make install_jsregexp",
+ -- },
+ -- "mfussenegger/nvim-lint",
+ -- "saadparwaiz1/cmp_luasnip",
+ -- "j-hui/fidget.nvim",
+ -- { "folke/neoconf.nvim", cmd = "Neoconf", config = false, dependencies = { "nvim-lspconfig" } },
+ -- {
+ -- "folke/lazydev.nvim",
+ -- ft = "lua", -- only load on lua files
+ -- opts = {
+ -- library = {
+ -- -- See the configuration section for more details
+ -- -- Load luvit types when the `vim.uv` word is found
+ -- { path = "${3rd}/luv/library", words = { "vim%.uv" } },
+ -- },
+ -- },
+ -- },
+ -- "stevearc/conform.nvim",
+ -- "saghen/blink.cmp",
+ -- },
+ -- init = function()
+ -- local wk = require("which-key")
+ -- wk.add({
+ -- mode = { "n", "v", "x" },
+ -- { "<leader>tf", group = "Format" },
+ -- })
+ -- end,
+ -- opts = {
+ -- servers = {
+ -- lua_ls = {
+ -- settings = {
+ -- Lua = {
+ -- workspace = {
+ -- checkThirdParty = false,
+ -- },
+ -- codeLens = {
+ -- enable = true,
+ -- },
+ -- completion = {
+ -- callSnippet = "Replace",
+ -- },
+ -- doc = {
+ -- privateName = { "^_" },
+ -- },
+ -- hint = {
+ -- enable = true,
+ -- setType = false,
+ -- paramType = true,
+ -- paramName = "Disable",
+ -- semicolon = "Disable",
+ -- arrayIndex = "Disable",
+ -- },
+ -- runtime = { version = "Lua 5.4" },
+ -- diagnostics = {
+ -- globals = { "bit", "vim", "it", "describe", "before_each", "after_each" },
+ -- },
+ -- },
+ -- },
+ -- },
+ -- pyright = {
+ -- settings = {
+ -- python = {
+ -- disableLanguageServices = false,
+ -- disableOrganizeImports = false,
+ -- },
+ -- },
+ -- },
+ -- },
+ -- },
+ -- config = function(_, opts)
+ -- local cmp = require("blink.cmp")
+ -- local lspconfig = require("lspconfig")
+ --
+ -- require("mason").setup()
+ --
+ -- for server, config in pairs(opts.servers) do
+ -- -- passing config.capabilities to blink.cmp merges with the capabilities in your
+ -- -- `opts[server].capabilities, if you've defined it
+ -- config.capabilities = cmp.get_lsp_capabilities(config.capabilities)
+ -- lspconfig[server].setup(config)
+ -- require("mason-lspconfig").setup({
+ -- ensure_installed = { server },
+ -- handlers = {
+ -- [server] = function()
+ -- lspconfig[server].setup(config)
+ -- end,
+ -- },
+ -- })
+ -- end
+ --
+ -- require("fidget").setup({
+ -- progress = {
+ -- poll_rate = 0, -- How and when to poll for progress messages
+ -- suppress_on_insert = true, -- Suppress new messages while in insert mode
+ -- ignore_done_already = true, -- Ignore new tasks that are already complete
+ -- ignore_empty_message = true, -- Ignore new tasks that don't contain a message
+ -- clear_on_detach = function(client_id) -- Clear notification group when LSP server detaches
+ -- local client = vim.lsp.get_client_by_id(client_id)
+ -- return client and client.name or nil
+ -- end,
+ -- ignore = { "lua_ls" },
+ -- },
+ -- notification = {
+ -- window = {
+ -- normal_hl = "Comment", -- Base highlight group in the notification window
+ -- winblend = 0, -- Background color opacity in the notification window
+ -- border = "none", -- Border around the notification window
+ -- zindex = 45, -- Stacking priority of the notification window
+ -- max_width = 0, -- Maximum width of the notification window
+ -- max_height = 0, -- Maximum height of the notification window
+ -- x_padding = 1, -- Padding from right edge of window boundary
+ -- y_padding = 0, -- Padding from bottom edge of window boundary
+ -- align = "bottom", -- How to align the notification window
+ -- relative = "editor", -- What the notification window position is relative to
+ -- },
+ -- },
+ -- integration = {
+ -- ["nvim-tree"] = {
+ -- enable = false, -- Integrate with nvim-tree/nvim-tree.lua (if installed)
+ -- },
+ -- },
+ -- })
+ --
+ -- local lint = require("lint")
+ -- lint.linters_by_ft = {
+ -- javascript = { "eslint_d" },
+ -- typescript = { "eslint_d" },
+ -- javascriptreact = { "eslint_d" },
+ -- typescriptreact = { "eslint_d" },
+ -- svelte = { "eslint_d" },
+ -- python = { "pylint" },
+ -- sh = { "shellcheck" },
+ -- }
+ --
+ -- local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true })
+ -- vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave", "TextChanged" }, {
+ -- group = lint_augroup,
+ -- callback = function()
+ -- lint.try_lint()
+ -- end,
+ -- })
+ --
+ -- require("mason-tool-installer").setup({
+ -- ensure_installed = {
+ -- "beautysh", -- zsh formatter
+ -- "black", -- python formatter
+ -- "debugpy", -- python debuger
+ -- "eslint_d", -- eslint linter
+ -- "isort", -- python formatter
+ -- "markdown-toc", -- markdown toc
+ -- "prettier", -- prettier formatter
+ -- "pylint", -- python linter
+ -- "ruff", -- python formatter
+ -- "shellcheck", -- bash lint
+ -- "shfmt", -- sh formatter
+ -- "stylua", -- lua formatter
+ -- },
+ -- integrations = {
+ -- ["mason-lspconfig"] = true,
+ -- ["mason-null-ls"] = false,
+ -- ["mason-nvim-dap"] = true,
+ -- },
+ -- })
+ --
+ -- vim.diagnostic.config({
+ -- -- update_in_insert = true,
+ -- float = {
+ -- focusable = false,
+ -- style = "minimal",
+ -- border = "rounded",
+ -- source = "always",
+ -- header = "",
+ -- prefix = "",
+ -- },
+ -- })
+ --
+ -- require("conform").setup({
+ -- formatters_by_ft = {
+ -- bash = { "shfmt" },
+ -- css = { "prettier" },
+ -- graphql = { "prettier" },
+ -- html = { "prettier" },
+ -- javascript = { "prettier" },
+ -- javascriptreact = { "prettier" },
+ -- json = { "prettier" },
+ -- liquid = { "prettier" },
+ -- lua = { "stylua" },
+ -- markdown = { "prettier" },
+ -- python = { "ruff", "isort", "black" },
+ -- sh = { "shfmt" },
+ -- svelte = { "prettier" },
+ -- typescript = { "prettier" },
+ -- typescriptreact = { "prettier" },
+ -- yaml = { "prettier" },
+ -- zsh = { "beautysh" },
+ -- },
+ -- default_format_opts = {},
+ -- format_on_save = function(bufnr)
+ -- -- Disable with a global or buffer-local variable
+ -- if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
+ -- return
+ -- end
+ -- return { lsp_format = "fallback", timeout_ms = 1000, async = false }
+ -- end,
+ -- })
+ --
+ -- vim.api.nvim_create_user_command("FormatDisable", function(args)
+ -- if args.bang then
+ -- vim.b.disable_autoformat = true
+ -- else
+ -- vim.g.disable_autoformat = true
+ -- end
+ -- end, {
+ -- desc = "Disable autoformat-on-save",
+ -- bang = true,
+ -- })
+ -- vim.api.nvim_create_user_command("FormatEnable", function()
+ -- vim.b.disable_autoformat = false
+ -- vim.g.disable_autoformat = false
+ -- end, {
+ -- desc = "Re-enable autoformat-on-save",
+ -- })
+ -- end,
+ -- keys = {
+ -- {
+ -- mode = { "n", "v" },
+ -- "<leader>lf",
+ -- function()
+ -- require("conform").format({ async = true })
+ -- end,
+ -- desc = "Format buffer by lsp",
+ -- },
+ -- {
+ -- "<leader>ci",
+ -- "<cmd>PyrightOrganizeImports<cr>",
+ -- desc = "Organize imports",
+ -- },
+ -- {
+ -- "<leader>bl",
+ -- function()
+ -- require("lint").try_lint()
+ -- end,
+ -- desc = "Buffer lint",
+ -- },
+ -- {
+ -- "<leader>le",
+ -- "<cmd>FormatEnable<CR>",
+ -- desc = "Enable format",
+ -- },
+ -- {
+ -- "<leader>ld",
+ -- "<cmd>FormatDisable<CR>",
+ -- desc = "Disable format",
+ -- },
+ -- {
+ -- "<leader>lD",
+ -- "<cmd>FormatDisable!<CR>",
+ -- desc = "Disable current buffer format",
+ -- },
+ -- },
+ -- },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lualine.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lualine.lua
new file mode 100644
index 0000000..e9323b5
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/lualine.lua
@@ -0,0 +1,259 @@
+return {
+ "nvim-lualine/lualine.nvim",
+ dependencies = { "nvim-tree/nvim-web-devicons" },
+ config = function()
+ local navic = require("nvim-navic")
+ require("lualine").setup({
+ options = {
+ icons_enabled = true,
+ theme = "auto",
+ section_separators = { left = "", right = "" },
+ component_separators = { left = "", right = "" },
+ disabled_filetypes = {
+ statusline = {},
+ winbar = {},
+ },
+ ignore_focus = {},
+ always_divide_middle = true,
+ globalstatus = false,
+ refresh = {
+ statusline = 100,
+ tabline = 100,
+ winbar = 100,
+ },
+ },
+ sections = {
+ lualine_a = {
+ {
+ "mode",
+ fmt = function(str)
+ return str:sub(1, 1)
+ end,
+ },
+ {
+ function()
+ local has_noice, noice = pcall(require, "noice")
+ if has_noice and noice.api and noice.api.status and noice.api.status.mode then
+ return noice.api.status.mode.get() or ""
+ else
+ return ""
+ end
+ end,
+ cond = function()
+ local has_noice, noice = pcall(require, "noice")
+ return has_noice
+ and noice.api
+ and noice.api.status
+ and noice.api.status.mode
+ and noice.api.status.mode.has()
+ end,
+ color = { bg = "#ff9e64" },
+ },
+ },
+ lualine_b = {
+ "branch",
+ {
+ "diff",
+ colored = true, -- Displays a colored diff status if set to true
+ diff_color = {
+ -- Same color values as the general color option can be used here.
+ added = "LuaLineDiffAdd", -- Changes the diff's added color
+ modified = "LuaLineDiffChange", -- Changes the diff's modified color
+ removed = "LuaLineDiffDelete", -- Changes the diff's removed color you
+ },
+ symbols = { added = "+", modified = "~", removed = "-" }, -- Changes the symbols used by the diff.
+ source = nil, -- A function that works as a data source for diff.
+ -- It must return a table as such:
+ -- { added = add_count, modified = modified_count, removed = removed_count }
+ -- or nil on failure. count <= 0 won't be displayed.
+ },
+ {
+ "diagnostics",
+ -- Table of diagnostic sources, available sources are:
+ -- 'nvim_lsp', 'nvim_diagnostic', 'nvim_workspace_diagnostic', 'coc', 'ale', 'vim_lsp'.
+ -- or a function that returns a table as such:
+ -- { error=error_cnt, warn=warn_cnt, info=info_cnt, hint=hint_cnt }
+ sources = { "nvim_lsp", "nvim_diagnostic", "nvim_workspace_diagnostic", "coc" },
+
+ -- Displays diagnostics for the defined severity types
+ sections = { "error", "warn", "info", "hint" },
+
+ diagnostics_color = {
+ -- Same values as the general color option can be used here.
+ error = "DiagnosticError", -- Changes diagnostics' error color.
+ warn = "DiagnosticWarn", -- Changes diagnostics' warn color.
+ info = "DiagnosticInfo", -- Changes diagnostics' info color.
+ hint = "DiagnosticHint", -- Changes diagnostics' hint color.
+ },
+ symbols = { error = "E", warn = "W", info = "I", hint = "H" },
+ colored = true, -- Displays diagnostics status in color if set to true.
+ update_in_insert = true, -- Update diagnostics in insert mode.
+ always_visible = false, -- Show diagnostics even if there are none.
+ },
+ },
+ lualine_c = {
+ {
+ "filename",
+ file_status = true, -- Displays file status (readonly status, modified status)
+ newfile_status = true, -- Display new file status (new file means no write after created)
+ path = 3, -- 0: Just the filename
+ -- 1: Relative path
+ -- 2: Absolute path
+ -- 3: Absolute path, with tilde as the home directory
+ -- 4: Filename and parent dir, with tilde as the home directory
+
+ shorting_target = 40, -- Shortens path to leave 40 spaces in the window
+ -- for other components. (terrible name, any suggestions?)
+ symbols = {
+ modified = "[*]", -- Text to show when the file is modified.
+ readonly = "[r]", -- Text to show when the file is non-modifiable or readonly.
+ unnamed = "[?]", -- Text to show for unnamed buffers.
+ newfile = "[%%]", -- Text to show for newly created file before first write
+ },
+ },
+ },
+ lualine_x = {
+ {
+ function()
+ local has_noice, noice = pcall(require, "noice")
+ if has_noice and noice.api and noice.api.status and noice.api.status.mode then
+ return noice.api.status.search.get() or ""
+ else
+ return ""
+ end
+ end,
+ cond = function()
+ local has_noice, noice = pcall(require, "noice")
+ return has_noice
+ and noice.api
+ and noice.api.status
+ and noice.api.status.search
+ and noice.api.status.search.has()
+ end,
+ color = { bg = "#ff9e64" },
+ },
+ "copilot",
+ {
+ function()
+ return require("molten.status").initialized()
+ end,
+ },
+ "encoding",
+ "fileformat",
+ {
+ "filetype",
+ colored = true, -- Displays filetype icon in color if set to true
+ icon_only = true, -- Display only an icon for filetype
+ icon = { align = "right" }, -- Display filetype icon on the right hand side
+ -- icon = {'X', align='right'}
+ -- Icon string ^ in table is ignored in filetype component
+ },
+ },
+ lualine_y = {
+ "progress",
+ },
+ lualine_z = {
+ {
+ function()
+ local is_tmux = os.getenv("TMUX") ~= nil
+ if is_tmux then
+ return ""
+ end
+ return os.date("%H:%M")
+ end,
+ },
+ },
+ },
+ inactive_sections = {},
+ tabline = {
+ lualine_a = {
+ {
+ "tabs",
+ tab_max_length = 40, -- Maximum width of each tab. The content will be shorten dynamically (example: apple/orange -> a/orange)
+ max_length = vim.o.columns / 3, -- Maximum width of tabs component.
+ -- Note:
+ -- It can also be a function that returns
+ -- the value of `max_length` dynamically.
+ mode = 0, -- 0: Shows tab_nr
+ -- 1: Shows tab_name
+ -- 2: Shows tab_nr + tab_name
+
+ path = nil, -- 0: just shows the filename
+ -- 1: shows the relative path and shorten $HOME to ~
+ -- 2: shows the full path
+ -- 3: shows the full path and shorten $HOME to ~
+
+ -- Automatically updates active tab color to match color of other components (will be overidden if buffers_color is set)
+ use_mode_colors = true,
+
+ -- tabs_color = {
+ -- -- Same values as the general color option can be used here.
+ -- active = "lualine_{section}_normal", -- Color for active tab.
+ -- inactive = "lualine_{section}_inactive", -- Color for inactive tab.
+ -- },
+ --
+ show_modified_status = false, -- Shows a symbol next to the tab name if the file has been modified.
+ symbols = {
+ modified = "*", -- Text to show when the file is modified.
+ },
+
+ fmt = function(name, context)
+ -- Show + if buffer is modified in tab
+ local buflist = vim.fn.tabpagebuflist(context.tabnr)
+ local winnr = vim.fn.tabpagewinnr(context.tabnr)
+ local bufnr = buflist[winnr]
+ local mod = vim.fn.getbufvar(bufnr, "&mod")
+
+ return name .. (mod == 1 and " +" or "")
+ end,
+ },
+ },
+ lualine_b = {
+ {
+ function()
+ local function buffer_status()
+ local buffers = vim.fn.getbufinfo({ buflisted = true })
+ local current_buf = vim.fn.bufnr()
+ local current_buffer_index = 0
+ local modified_symbol = vim.bo.modified and "●" or ""
+ for i, buf in ipairs(buffers) do
+ if buf.bufnr == current_buf then
+ current_buffer_index = i
+ break
+ end
+ end
+ return string.format("%s%d/%d", modified_symbol, current_buffer_index, #buffers)
+ end
+ return buffer_status()
+ end,
+ },
+ },
+ lualine_c = {
+ {
+ function()
+ return navic.get_location()
+ end,
+ cond = function()
+ return navic.is_available()
+ end,
+ },
+ },
+ lualine_x = {},
+ lualine_y = {},
+ lualine_z = {},
+ },
+ winbar = {},
+ inactive_winbar = {},
+ extensions = {},
+ })
+
+ local lualine_hidden = true
+ vim.keymap.set({ "n", "v" }, "<leader>zl", function()
+ lualine_hidden = not lualine_hidden
+ require("lualine").hide({
+ place = { "statusline", "tabline", "winbar" },
+ unhide = lualine_hidden,
+ })
+ end, { desc = "Toggle lualine" })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/markdown.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/markdown.lua
new file mode 100644
index 0000000..fa5051e
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/markdown.lua
@@ -0,0 +1,454 @@
+-- Select the current cell
+local function select_cell()
+ local bufnr = vim.api.nvim_get_current_buf()
+ local current_row = vim.api.nvim_win_get_cursor(0)[1]
+ local current_col = vim.api.nvim_win_get_cursor(0)[2]
+
+ local start_line = nil
+ local end_line = nil
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+
+ -- Find the start of the cell (looking for opening markers or headers)
+ for line = current_row, 1, -1 do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:match("^```%s*[%w_%-]+") or line_content:match("^%s*#+%s") then
+ start_line = line
+ break
+ end
+ end
+
+ -- If no start line is found, assume start of the file
+ if not start_line then
+ start_line = 1
+ end
+
+ -- Find the end of the cell (looking for the next opening marker or header)
+ for line = start_line + 1, line_count do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:match("^```%s*[%w_%-]+") or line_content:match("^%s*#+%s") then
+ end_line = line - 1
+ break
+ end
+ end
+
+ -- If no end line is found, assume end of the file
+ if not end_line then
+ end_line = line_count
+ end
+
+ return current_row, current_col, start_line, end_line
+end
+
+-- Delete the current cell
+local function delete_cell()
+ local _, _, start_line, end_line = select_cell() -- Use select_cell to get start and end lines of the cell
+ if start_line and end_line then
+ -- Move cursor to the start of the cell
+ vim.api.nvim_win_set_cursor(0, { start_line, 0 })
+
+ -- Enter visual line mode to select the cell
+ vim.cmd("normal! V")
+ -- Move cursor to the end of the cell to extend selection
+ vim.api.nvim_win_set_cursor(0, { end_line, 0 })
+
+ -- Delete the selected lines
+ vim.cmd("normal! d")
+ end
+end
+
+-- Navigate to the next or previous cell
+local function navigate_cell(up)
+ local bufnr = vim.api.nvim_get_current_buf()
+ local line_count = vim.api.nvim_buf_line_count(bufnr)
+ local _, _, start_line, end_line = select_cell() -- Get the start and end lines of the current cell
+
+ local target_line = nil
+
+ if up then
+ -- Find the previous cell start, skipping any closing markers
+ for line = start_line - 1, 1, -1 do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:match("^```%s*[%w_%-]+") or line_content:match("^%s*#+%s") then
+ target_line = line
+ break
+ end
+ end
+ else
+ -- Find the next cell start, skipping any closing markers
+ for line = end_line + 1, line_count do
+ local line_content = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)[1]
+ if line_content:match("^```%s*[%w_%-]+") or line_content:match("^%s*#+%s") then
+ target_line = line
+ break
+ end
+ end
+ end
+
+ -- Navigate to the target line if found, otherwise stay at the current position
+ if target_line then
+ -- If the target is a code block, move cursor to the line right after the opening marker
+ local target_line_content = vim.api.nvim_buf_get_lines(bufnr, target_line - 1, target_line, false)[1]
+ if target_line_content:match("^```%s*[%w_%-]+") then
+ vim.api.nvim_win_set_cursor(0, { target_line + 1, 0 }) -- Move inside the code block
+ else
+ vim.api.nvim_win_set_cursor(0, { target_line, 0 }) -- Move to the markdown header line
+ end
+ else
+ if up then
+ vim.api.nvim_win_set_cursor(0, { 1, 0 }) -- Move to start of file if no previous cell found
+ else
+ vim.api.nvim_win_set_cursor(0, { line_count, 0 }) -- Move to end of file if no next cell found
+ end
+ end
+end
+
+-- Insert a new cell with specific content
+local function insert_cell(content)
+ local _, _, _, end_line = select_cell()
+ local bufnr = vim.api.nvim_get_current_buf()
+ local line = end_line
+ if end_line ~= 1 then
+ line = end_line - 1
+ vim.api.nvim_win_set_cursor(0, { end_line - 1, 0 })
+ else
+ line = end_line
+ vim.api.nvim_win_set_cursor(0, { end_line, 0 })
+ end
+
+ vim.cmd("normal!2o")
+ vim.api.nvim_buf_set_lines(bufnr, line, line + 1, false, { content })
+ vim.cmd("normal!2o")
+ vim.cmd("normal!k")
+end
+
+-- Insert a new code cell
+local function insert_code_cell()
+ insert_cell("```python") -- For regular code cells
+end
+
+return {
+ -- {
+ -- "ixru/nvim-markdown",
+ -- config = function()
+ -- vim.g.vim_markdown_no_default_key_mappings = 1
+ -- end,
+ -- },
+ {
+ "MeanderingProgrammer/render-markdown.nvim",
+ enabled = true,
+ -- dependencies = { "nvim-treesitter/nvim-treesitter", "echasnovski/mini.nvim" }, -- if you use the mini.nvim suite
+ dependencies = { "nvim-treesitter/nvim-treesitter", "echasnovski/mini.icons" }, -- if you use standalone mini plugins
+ -- dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, -- if you prefer nvim-web-devicons
+ ---@module 'render-markdown'
+ ---@type render.md.UserConfig
+ opts = {},
+ config = function()
+ -- require("obsidian").get_client().opts.ui.enable = false
+ -- vim.api.nvim_buf_clear_namespace(0, vim.api.nvim_get_namespaces()["ObsidianUI"], 0, -1)
+ require("render-markdown").setup({
+ bullet = {
+ -- Turn on / off list bullet rendering
+ enabled = true,
+ },
+ heading = {
+ sign = false,
+ icons = { "󰎤 ", "󰎧 ", "󰎪 ", "󰎭 ", "󰎱 ", "󰎳 " },
+ },
+ file_types = { "markdown", "vimwiki" },
+ })
+ vim.treesitter.language.register("markdown", "vimwiki")
+ end,
+ },
+ {
+ -- Install markdown preview, use npx if available.
+ "iamcco/markdown-preview.nvim",
+ cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
+ ft = { "markdown" },
+ build = function(plugin)
+ if vim.fn.executable("npx") then
+ vim.cmd("!cd " .. plugin.dir .. " && cd app && npx --yes yarn install")
+ else
+ vim.cmd([[Lazy load markdown-preview.nvim]])
+ vim.fn["mkdp#util#install"]()
+ end
+ end,
+ init = function()
+ if vim.fn.executable("npx") then
+ vim.g.mkdp_filetypes = { "markdown" }
+ end
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>m", group = "Markdown/Map" },
+ })
+ end,
+ keys = {
+ { "<leader>mp", "<cmd>MarkdownPreview<CR>", desc = "Markdown Preview" },
+ { "<leader>mx", "<cmd>MarkdownPreviewStop<CR>", desc = "Markdown Stop" },
+ { "<leader>md", "<cmd>MarkdownPreviewToggle<CR>", desc = "Markdown Toggle" },
+ },
+ },
+ {
+ "dhruvasagar/vim-open-url",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "g<CR>", group = "Search word under curosr" },
+ { "gB", group = "Open url in browser" },
+ { "gG", group = "Google search word under cursor" },
+ { "gW", group = "Wikipedia search word under cursor" },
+ })
+ end,
+ },
+ {
+ "AckslD/nvim-FeMaco.lua",
+ config = function()
+ require("femaco").setup()
+ end,
+ },
+ {
+ "goerz/jupytext.vim",
+ lazy = false,
+ build = "pip install jupytext",
+ dependencies = { "neovim/nvim-lspconfig" },
+ config = function()
+ -- The destination format: 'ipynb', 'markdown' or 'script', or a file extension: 'md', 'Rmd', 'jl', 'py', 'R', ..., 'auto' (script
+ -- extension matching the notebook language), or a combination of an extension and a format name, e.g. md:markdown, md:pandoc,
+ -- md:myst or py:percent, py:light, py:nomarker, py:hydrogen, py:sphinx. The default format for scripts is the 'light' format,
+ -- which uses few cell markers (none when possible). Alternatively, a format compatible with many editors is the 'percent' format,
+ -- which uses '# %%' as cell markers. The main formats (markdown, light, percent) preserve notebooks and text documents in a
+ -- roundtrip. Use the --test and and --test-strict commands to test the roundtrip on your files. Read more about the available
+ -- formats at https://jupytext.readthedocs.io/en/latest/formats.html (default: None)
+ vim.g.jupytext_fmt = "markdown"
+ end,
+ keys = {
+ { "<A-i>", insert_code_cell, desc = "Insert Code Cell" },
+ { "<A-x>", delete_cell, desc = "Delete Cell" },
+ {
+ "<S-Tab>",
+ function()
+ navigate_cell(true)
+ end,
+ desc = "Previous Cell",
+ },
+ { "<Tab>", navigate_cell, desc = "Next Cell" },
+ },
+ },
+ {
+ "vhyrro/luarocks.nvim",
+ priority = 1001, -- this plugin needs to run before anything else
+ init = function()
+ package.path = package.path
+ .. ";"
+ .. vim.fn.expand("$HOME")
+ .. "/.config/luarocks/share/lua/5.1/magick/init.lua;"
+ end,
+ opt = {
+ rocks = { "magick" },
+ },
+ },
+ { "benlubas/image-save.nvim", cmd = "SaveImage" },
+ {
+ "3rd/image.nvim",
+ dependencies = { "leafo/magick", "vhyrro/luarocks.nvim" },
+ config = function()
+ require("image").setup({
+ backend = "ueberzug", -- or "kitty", whatever backend you would like to use
+ processor = "magick_rock", -- or "magick_cli"
+ integrations = {
+ markdown = {
+ enabled = true,
+ clear_in_insert_mode = false,
+ download_remote_images = false,
+ only_render_image_at_cursor = false,
+ floating_windows = false, -- if true, images will be rendered in floating markdown windows
+ filetypes = { "markdown", "quarto" }, -- markdown extensions (ie. quarto) can go here
+ },
+ neorg = {
+ enabled = true,
+ filetypes = { "norg" },
+ },
+ typst = {
+ enabled = true,
+ filetypes = { "typst" },
+ },
+ html = {
+ enabled = false,
+ },
+ css = {
+ enabled = false,
+ },
+ },
+ max_width = 100,
+ max_height = 8,
+ max_height_window_percentage = math.huge,
+ max_width_window_percentage = math.huge,
+ window_overlap_clear_enabled = true, -- toggles images when windows are overlapped
+ window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "fidget", "" },
+ editor_only_render_when_focused = true, -- auto show/hide images when the editor gains/looses focus
+ tmux_show_only_in_active_window = true, -- auto show/hide images in the correct Tmux window (needs visual-activity off)
+ hijack_file_patterns = { "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.avif" }, -- render image files as images when opened
+ })
+ end,
+ },
+ {
+ "quarto-dev/quarto-nvim",
+ dependencies = {
+ {
+ "jmbuhr/otter.nvim",
+ lazy = false,
+ dependencies = {
+ "nvim-treesitter/nvim-treesitter",
+ },
+ opts = {},
+ config = function()
+ require("otter").setup()
+ end,
+ },
+ "nvim-cmp",
+ "neovim/nvim-lspconfig",
+ "nvim-treesitter/nvim-treesitter",
+ "otter.nvim",
+ },
+ ft = { "quarto", "markdown" },
+ command = "QuartoActivate",
+ config = function()
+ require("quarto").setup({
+ lspFeatures = {
+ languages = { "r", "python", "rust", "lua" },
+ chunks = "all",
+ diagnostics = {
+ enabled = true,
+ triggers = { "BufWritePost" },
+ },
+ completion = {
+ enabled = true,
+ },
+ },
+ keymap = {
+ hover = "H",
+ definition = "gd",
+ rename = "<leader>rn",
+ references = "gr",
+ format = "<leader>gf",
+ },
+ codeRunner = {
+ enabled = true,
+ default_method = "molten",
+ ft_runners = {
+ bash = "slime",
+ python = "molten",
+ },
+ never_run = { "yaml" }, -- filetypes which are never sent to a code runner
+ },
+ })
+ local runner = require("quarto.runner")
+ vim.keymap.set("n", "<leader>jc", runner.run_cell, { silent = true, desc = "Run cell" })
+ vim.keymap.set("n", "<leader>jC", runner.run_above, { silent = true, desc = "Run above cell" })
+ vim.keymap.set("n", "<leader>jl", runner.run_line, { silent = true, desc = "Run line" })
+ vim.keymap.set("v", "<leader>jv", runner.run_range, { silent = true, desc = "Run block" })
+ vim.keymap.set("n", "<leader>jA", runner.run_all, { silent = true, desc = "Run all" })
+ vim.keymap.set(
+ "n",
+ "<leader>qp",
+ require("quarto").quartoPreview,
+ { noremap = true, silent = true, desc = "Preview the quarto document" }
+ )
+ -- to create a cell in insert mode, I have the ` snippet
+ vim.keymap.set(
+ "n",
+ "<leader>cc",
+ "i```python\n```<Esc>O",
+ { silent = true, desc = "Create a new code cell" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>cs",
+ "i```\r\r```{}<left>",
+ { noremap = true, silent = true, desc = "Split code cell" }
+ )
+ end,
+ },
+ {
+ "benlubas/molten-nvim",
+ version = "^1.0.0", -- use version <2.0.0 to avoid breaking changes
+ dependencies = { "3rd/image.nvim" },
+ build = ":UpdateRemotePlugins",
+ init = function()
+ vim.g.molten_auto_image_popup = true
+ vim.g.molten_auto_init_behavior = "raise"
+ vim.g.molten_auto_open_html_in_browser = false
+ -- I find auto open annoying, keep in mind setting this option will require setting
+ -- a keybind for `:noautocmd MoltenEnterOutput` to open the output again
+ vim.g.molten_auto_open_output = true
+ vim.g.molten_cover_empty_lines = false
+ vim.g.molten_cover_lines_starting_with = {}
+ vim.g.molten_copy_output = false
+ vim.g.molten_enter_output_behavior = "open_then_enter"
+ -- this guide will be using image.nvim
+ -- Don't forget to setup and install the plugin if you want to view image outputs
+ vim.g.molten_image_provider = "image.nvim"
+ vim.g.molten_output_show_more = false
+ vim.g.molten_output_win_max_height = 30
+ vim.g.molten_output_win_style = "minimal"
+ -- this will make it so the output shows up below the \`\`\` cell delimiter
+ vim.g.molten_virt_lines_off_by_1 = true
+ -- Output as virtual text. Allows outputs to always be shown, works with images, but can
+ -- be buggy with longer images
+ vim.g.molten_virt_text_output = true
+ -- optional, works for virt text and the output window
+ vim.g.molten_wrap_output = true
+ vim.g.molten_virt_text_max_lines = 20
+
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>j", group = "Molten (Jupyter)" },
+ })
+ end,
+ config = function()
+ -- image nvim options table. Pass to `require('image').setup`
+ vim.keymap.set("n", "<leader>jJ", ":MoltenInit<CR>", { silent = true, desc = "Init molten" })
+ vim.keymap.set(
+ "n",
+ "<leader>jo",
+ ":MoltenEvaluateOperator<CR>",
+ { silent = true, desc = "Evaluate operator" }
+ )
+ vim.keymap.set("n", "<leader>jL", ":MoltenEvaluateLine<CR>", { silent = true, desc = "Evaluate line" })
+ vim.keymap.set("n", "<leader>jr", ":MoltenReevaluateCell<CR>", { silent = true, desc = "Re-evaluate cell" })
+ vim.keymap.set(
+ "v",
+ "<leader>jV",
+ ":<C-u>MoltenEvaluateVisual<CR>gv<Esc>",
+ { silent = true, desc = "Evaluate visual block" }
+ )
+ vim.keymap.set("n", "<leader>jd", ":MoltenDelete<CR>", { silent = true, desc = "Delete molten" })
+ vim.keymap.set("n", "<leader>jh", ":MoltenHideOutput<CR>", { silent = true, desc = "Hide output" })
+ vim.keymap.set(
+ "n",
+ "<leader>jm",
+ ":noautocmd MoltenEnterOutput<CR>",
+ { silent = true, desc = "Enter output" }
+ )
+ vim.api.nvim_create_autocmd("User", {
+ pattern = "MoltenInitPost",
+ callback = function()
+ require("quarto").activate()
+ end,
+ })
+ vim.keymap.set("n", "<leader>ji", ":MoltenImagePopup<CR>", { silent = true, desc = "Pop-up image" })
+ vim.keymap.set("n", "<leader>jw", ":MoltenOpenInBrowser<CR>", { silent = true, desc = "Open in browser" })
+ vim.keymap.set("n", "<leader>jj", function()
+ local venv = os.getenv("WORKON_HOME")
+ if venv ~= nil then
+ venv = string.match(venv, "/.+/(.+)")
+ vim.cmd(("MoltenInit %s"):format(venv))
+ else
+ vim.cmd("MoltenInit python3")
+ end
+ end, { desc = "Init default molten" })
+ end,
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/marks.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/marks.lua
new file mode 100644
index 0000000..d59d0f1
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/marks.lua
@@ -0,0 +1,16 @@
+return {
+ "chentoast/marks.nvim",
+ config = function()
+ require("marks").setup()
+ end,
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ {
+ mode = { "n", "v" },
+ { "m", group = "Marks" },
+ { "dm", desc = "Delete marks" },
+ },
+ })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/mini.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/mini.lua
new file mode 100644
index 0000000..d520afc
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/mini.lua
@@ -0,0 +1,1132 @@
+-- Updated pattern to match what Echasnovski has in the documentation
+-- https://github.com/echasnovski/mini.nvim/blob/c6eede272cfdb9b804e40dc43bb9bff53f38ed8a/doc/mini-files.txt#L508-L529
+
+-- Define a function to update MiniJump highlight based on Search
+local function update_mini_jump_highlight()
+ local search_hl = vim.api.nvim_get_hl(0, { name = "Search" })
+ vim.api.nvim_set_hl(0, "MiniJump", {
+ fg = search_hl.fg,
+ bg = search_hl.bg,
+ bold = search_hl.bold or false,
+ })
+end
+
+return {
+ {
+ "echasnovski/mini.ai",
+ version = false,
+ config = function()
+ require("mini.ai").setup({
+ -- Table with textobject id as fields, textobject specification as values.
+ -- Also use this to disable builtin textobjects. See |MiniAi.config|.
+ custom_textobjects = nil,
+
+ -- Module mappings. Use `''` (empty string) to disable one.
+ mappings = {
+ -- Main textobject prefixes
+ around = "a",
+ inside = "i",
+
+ -- Next/last variants
+ around_next = "an",
+ inside_next = "in",
+ around_last = "al",
+ inside_last = "il",
+
+ -- Move cursor to corresponding edge of `a` textobject
+ goto_left = "g[",
+ goto_right = "g]",
+ },
+
+ -- Number of lines within which textobject is searched
+ n_lines = 50,
+
+ -- How to search for object (first inside current line, then inside
+ -- neighborhood). One of 'cover', 'cover_or_next', 'cover_or_prev',
+ -- 'cover_or_nearest', 'next', 'previous', 'nearest'.
+ search_method = "cover_or_next",
+
+ -- Whether to disable showing non-error feedback
+ -- This also affects (purely informational) helper messages shown after
+ -- idle time if user input is required.
+ silent = false,
+ })
+ end,
+ },
+ {
+ "echasnovski/mini.bracketed",
+ version = false,
+ config = function()
+ require("mini.bracketed").setup({
+ buffer = { suffix = "", options = {} },
+ comment = { suffix = "", options = {} },
+ conflict = { suffix = "", options = {} },
+ diagnostic = { suffix = "", options = {} },
+ file = { suffix = "", options = {} },
+ indent = { suffix = "", options = {} },
+ jump = { suffix = "", options = {} },
+ location = { suffix = "", options = {} },
+ oldfile = { suffix = "", options = {} },
+ quickfix = { suffix = "", options = {} },
+ treesitter = { suffix = "", options = {} },
+ undo = { suffix = "", options = {} },
+ window = { suffix = "", options = {} },
+ yank = { suffix = "", options = {} },
+ })
+
+ vim.keymap.set("n", "<leader>[B", "<Cmd>lua MiniBracketed.buffer('first')<cr>", { desc = "Buffer first" })
+ vim.keymap.set(
+ "n",
+ "<leader>[b",
+ "<Cmd>lua MiniBracketed.buffer('backward')<cr>",
+ { desc = "Buffer backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]b",
+ "<Cmd>lua MiniBracketed.buffer('forward')<cr>",
+ { desc = "Buffer forward" }
+ )
+ vim.keymap.set("n", "<leader>]B", "<Cmd>lua MiniBracketed.buffer('last')<cr>", { desc = "Buffer last" })
+ vim.keymap.set("n", "<leader>[C", "<Cmd>lua MiniBracketed.comment('first')<cr>", { desc = "Comment first" })
+ vim.keymap.set(
+ "n",
+ "<leader>[c",
+ "<Cmd>lua MiniBracketed.comment('backward')<cr>",
+ { desc = "Comment backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]c",
+ "<Cmd>lua MiniBracketed.comment('forward')<cr>",
+ { desc = "Comment forward" }
+ )
+ vim.keymap.set("n", "<leader>]C", "<Cmd>lua MiniBracketed.comment('last')<cr>", { desc = "Comment last" })
+ vim.keymap.set(
+ "n",
+ "<leader>[X",
+ "<Cmd>lua MiniBracketed.conflict('first')<cr>",
+ { desc = "Conflict first" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>[x",
+ "<Cmd>lua MiniBracketed.conflict('backward')<cr>",
+ { desc = "Conflict backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]x",
+ "<Cmd>lua MiniBracketed.conflict('forward')<cr>",
+ { desc = "Conflict forward" }
+ )
+ vim.keymap.set("n", "<leader>]X", "<Cmd>lua MiniBracketed.conflict('last')<cr>", { desc = "Conflict last" })
+ vim.keymap.set(
+ "n",
+ "<leader>[D",
+ "<Cmd>lua MiniBracketed.diagnostic('first')<cr>",
+ { desc = "Diagnostic first" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>[d",
+ "<Cmd>lua MiniBracketed.diagnostic('backward')<cr>",
+ { desc = "Diagnostic backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]d",
+ "<Cmd>lua MiniBracketed.diagnostic('forward')<cr>",
+ { desc = "Diagnostic forward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]D",
+ "<Cmd>lua MiniBracketed.diagnostic('last')<cr>",
+ { desc = "Diagnostic last" }
+ )
+ vim.keymap.set("n", "<leader>[F", "<Cmd>lua MiniBracketed.file('first')<cr>", { desc = "File first" })
+ vim.keymap.set("n", "<leader>[f", "<Cmd>lua MiniBracketed.file('backward')<cr>", { desc = "File backward" })
+ vim.keymap.set("n", "<leader>]f", "<Cmd>lua MiniBracketed.file('forward')<cr>", { desc = "File forward" })
+ vim.keymap.set("n", "<leader>]F", "<Cmd>lua MiniBracketed.file('last')<cr>", { desc = "File last" })
+ vim.keymap.set("n", "<leader>[I", "<Cmd>lua MiniBracketed.indent('first')<cr>", { desc = "Indent first" })
+ vim.keymap.set(
+ "n",
+ "<leader>[i",
+ "<Cmd>lua MiniBracketed.indent('backward')<cr>",
+ { desc = "Indent backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]i",
+ "<Cmd>lua MiniBracketed.indent('forward')<cr>",
+ { desc = "Indent forward" }
+ )
+ vim.keymap.set("n", "<leader>]I", "<Cmd>lua MiniBracketed.indent('last')<cr>", { desc = "Indent last" })
+ vim.keymap.set("n", "<leader>[J", "<Cmd>lua MiniBracketed.jump('first')<cr>", { desc = "Jump first" })
+ vim.keymap.set("n", "<leader>[j", "<Cmd>lua MiniBracketed.jump('backward')<cr>", { desc = "Jump backward" })
+ vim.keymap.set("n", "<leader>]j", "<Cmd>lua MiniBracketed.jump('forward')<cr>", { desc = "Jump forward" })
+ vim.keymap.set("n", "<leader>]J", "<Cmd>lua MiniBracketed.jump('last')<cr>", { desc = "Jump last" })
+ vim.keymap.set(
+ "n",
+ "<leader>[L",
+ "<Cmd>lua MiniBracketed.location('first')<cr>",
+ { desc = "Location first" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>[l",
+ "<Cmd>lua MiniBracketed.location('backward')<cr>",
+ { desc = "Location backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]l",
+ "<Cmd>lua MiniBracketed.location('forward')<cr>",
+ { desc = "Location forward" }
+ )
+ vim.keymap.set("n", "<leader>]L", "<Cmd>lua MiniBracketed.location('last')<cr>", { desc = "Location last" })
+ vim.keymap.set("n", "<leader>[O", "<Cmd>lua MiniBracketed.oldfile('first')<cr>", { desc = "Oldfile first" })
+ vim.keymap.set(
+ "n",
+ "<leader>[o",
+ "<Cmd>lua MiniBracketed.oldfile('backward')<cr>",
+ { desc = "Oldfile backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]o",
+ "<Cmd>lua MiniBracketed.oldfile('forward')<cr>",
+ { desc = "Oldfile forward" }
+ )
+ vim.keymap.set("n", "<leader>]O", "<Cmd>lua MiniBracketed.oldfile('last')<cr>", { desc = "Oldfile last" })
+ vim.keymap.set(
+ "n",
+ "<leader>[Q",
+ "<Cmd>lua MiniBracketed.quickfix('first')<cr>",
+ { desc = "Quickfix first" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>[q",
+ "<Cmd>lua MiniBracketed.quickfix('backward')<cr>",
+ { desc = "Quickfix backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]q",
+ "<Cmd>lua MiniBracketed.quickfix('forward')<cr>",
+ { desc = "Quickfix forward" }
+ )
+ vim.keymap.set("n", "<leader>]Q", "<Cmd>lua MiniBracketed.quickfix('last')<cr>", { desc = "Quickfix last" })
+ vim.keymap.set(
+ "n",
+ "<leader>[T",
+ "<Cmd>lua MiniBracketed.treesitter('first')<cr>",
+ { desc = "Treesitter first" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>[t",
+ "<Cmd>lua MiniBracketed.treesitter('backward')<cr>",
+ { desc = "Treesitter backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]t",
+ "<Cmd>lua MiniBracketed.treesitter('forward')<cr>",
+ { desc = "Treesitter forward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>]T",
+ "<Cmd>lua MiniBracketed.treesitter('last')<cr>",
+ { desc = "Treesitter last" }
+ )
+ vim.keymap.set("n", "<leader>[U", "<Cmd>lua MiniBracketed.undo('first')<cr>", { desc = "Undo first" })
+ vim.keymap.set("n", "<leader>[u", "<Cmd>lua MiniBracketed.undo('backward')<cr>", { desc = "Undo backward" })
+ vim.keymap.set("n", "<leader>]u", "<Cmd>lua MiniBracketed.undo('forward')<cr>", { desc = "Undo forward" })
+ vim.keymap.set("n", "<leader>]U", "<Cmd>lua MiniBracketed.undo('last')<cr>", { desc = "Undo last" })
+ vim.keymap.set("n", "<leader>w0", "<Cmd>lua MiniBracketed.window('first')<cr>", { desc = "Window first" })
+ vim.keymap.set(
+ "n",
+ "<leader>w[",
+ "<Cmd>lua MiniBracketed.window('backward')<cr>",
+ { desc = "Window backward" }
+ )
+ vim.keymap.set(
+ "n",
+ "<leader>w]",
+ "<Cmd>lua MiniBracketed.window('forward')<cr>",
+ { desc = "Window forward" }
+ )
+ vim.keymap.set("n", "<leader>w$", "<Cmd>lua MiniBracketed.window('last')<cr>", { desc = "Window last" })
+ vim.keymap.set("n", "<leader>[Y", "<Cmd>lua MiniBracketed.yank('first')<cr>", { desc = "Yank first" })
+ vim.keymap.set("n", "<leader>[y", "<Cmd>lua MiniBracketed.yank('backward')<cr>", { desc = "Yank backward" })
+ vim.keymap.set("n", "<leader>]y", "<Cmd>lua MiniBracketed.yank('forward')<cr>", { desc = "Yank forward" })
+ vim.keymap.set("n", "<leader>]Y", "<Cmd>lua MiniBracketed.yank('last')<cr>", { desc = "Yank last" })
+ end,
+ },
+ {
+ "echasnovski/mini.files",
+ opts = {
+ -- I didn't like the default mappings, so I modified them
+ -- Module mappings created only inside explorer.
+ -- Use `''` (empty string) to not create one.
+ mappings = {
+ close = "q",
+ -- Use this if you want to open several files
+ go_in = "l",
+ -- This opens the file, but quits out of mini.files (default L)
+ go_in_plus = "<CR>",
+ -- I swapped the following 2 (default go_out: h)
+ -- go_out_plus: when you go out, it shows you only 1 item to the right
+ -- go_out: shows you all the items to the right
+ go_out = "H",
+ go_out_plus = "h",
+ -- Default <BS>
+ reset = ",",
+ -- Default @
+ reveal_cwd = ".",
+ show_help = "g?",
+ -- Default =
+ synchronize = "s",
+ trim_left = "<",
+ trim_right = ">",
+ toggle_hidden = nil,
+ change_cwd = nil,
+ go_in_horizontal = nil,
+ go_in_vertical = nil,
+ go_in_horizontal_plus = nil,
+ go_in_vertical_plus = nil,
+ },
+ options = {
+ use_as_default_explorer = true,
+ permanent_delete = false,
+ },
+ windows = {
+ preview = true,
+ width_focus = 25,
+ width_preview = 40,
+ },
+ },
+ keys = {
+ {
+ "<leader>e",
+ function()
+ if not MiniFiles.close() then
+ require("mini.files").open(vim.api.nvim_buf_get_name(0), true)
+ end
+ end,
+ desc = "Open mini.files",
+ },
+ {
+ "<leader>E",
+ function()
+ require("mini.files").open(vim.uv.cwd(), true)
+ end,
+ desc = "Open mini.files (cwd)",
+ },
+ },
+ config = function(_, opts)
+ require("mini.files").setup(opts)
+
+ local show_dotfiles = true
+ local filter_show = function(fs_entry)
+ return true
+ end
+ local filter_hide = function(fs_entry)
+ return not vim.startswith(fs_entry.name, ".")
+ end
+
+ local toggle_dotfiles = function()
+ show_dotfiles = not show_dotfiles
+ local new_filter = show_dotfiles and filter_show or filter_hide
+ require("mini.files").refresh({ content = { filter = new_filter } })
+ end
+
+ local map_split = function(buf_id, lhs, direction, close_on_file)
+ local rhs = function()
+ local new_target_window
+ local cur_target_window = require("mini.files").get_explorer_state().arget_window
+
+ if cur_target_window ~= nil then
+ vim.api.nvim_win_call(cur_target_window, function()
+ vim.cmd("belowright " .. direction .. " split")
+ new_target_window = vim.api.nvim_get_current_win()
+ end)
+
+ require("mini.files").set_target_window(new_target_window)
+ require("mini.files").go_in({ close_on_file = close_on_file })
+ end
+ end
+
+ local desc = "Open in " .. direction .. " split"
+ if close_on_file then
+ desc = desc .. " and close"
+ end
+ vim.keymap.set("n", lhs, rhs, { buffer = buf_id, desc = desc })
+ end
+
+ local files_set_cwd = function()
+ local cur_entry_path = MiniFiles.get_fs_entry().path
+ local cur_directory = vim.fs.dirname(cur_entry_path)
+ if cur_directory ~= nil then
+ vim.fn.chdir(cur_directory)
+ end
+ end
+
+ local mini_files = require("mini.files")
+ local tmux_pane_function = require("thesiahxyz.utils.tmux").tmux_pane_function
+
+ local open_tmux_pane = function()
+ local curr_entry = mini_files.get_fs_entry()
+ if curr_entry then
+ if curr_entry.fs_type == "directory" then
+ tmux_pane_function(curr_entry.path)
+ elseif curr_entry.fs_type == "file" then
+ local parent_dir = vim.fn.fnamemodify(curr_entry.path, ":h")
+ tmux_pane_function(parent_dir)
+ elseif curr_entry.fs_type == "link" then
+ local resolved_path = vim.fn.resolve(curr_entry.path)
+ if vim.fn.isdirectory(resolved_path) == 1 then
+ tmux_pane_function(resolved_path)
+ else
+ local parent_dir = vim.fn.fnamemodify(resolved_path, ":h")
+ tmux_pane_function(parent_dir)
+ end
+ else
+ vim.notify("Unsupported file system entry type", vim.log.levels.WARN)
+ end
+ else
+ vim.notify("No entry selected", vim.log.levels.WARN)
+ end
+ end
+
+ local copy_to_clipboard = function()
+ local curr_entry = mini_files.get_fs_entry()
+ if curr_entry then
+ local path = curr_entry.path
+ -- Escape the path for shell command
+ local escaped_path = vim.fn.fnameescape(path)
+ local cmd = vim.fn.has("mac") == 1
+ and string.format([[osascript -e 'set the clipboard to POSIX file "%s"']], escaped_path)
+ or string.format([[echo -n %s | xclip -selection clipboard]], escaped_path)
+ local result = vim.fn.system(cmd)
+ if vim.v.shell_error ~= 0 then
+ vim.notify("Copy failed: " .. result, vim.log.levels.ERROR)
+ else
+ vim.notify(vim.fn.fnamemodify(path, ":t"), vim.log.levels.INFO)
+ vim.notify("Copied to system clipboard", vim.log.levels.INFO)
+ end
+ else
+ vim.notify("No file or directory selected", vim.log.levels.WARN)
+ end
+ end
+
+ local zip_and_copy_to_clipboard = function()
+ local curr_entry = require("mini.files").get_fs_entry()
+ if curr_entry then
+ local path = curr_entry.path
+ local name = vim.fn.fnamemodify(path, ":t") -- Extract the file or directory name
+ local parent_dir = vim.fn.fnamemodify(path, ":h") -- Get the parent directory
+ local timestamp = os.date("%y%m%d%H%M%S") -- Append timestamp to avoid duplicates
+ local zip_path = string.format("/tmp/%s_%s.zip", name, timestamp) -- Path in macOS's tmp directory
+ -- Create the zip file
+ local zip_cmd = string.format(
+ "cd %s && zip -r %s %s",
+ vim.fn.shellescape(parent_dir),
+ vim.fn.shellescape(zip_path),
+ vim.fn.shellescape(name)
+ )
+ local result = vim.fn.system(zip_cmd)
+ if vim.v.shell_error ~= 0 then
+ vim.notify("Failed to create zip file: " .. result, vim.log.levels.ERROR)
+ return
+ end
+ -- Copy the zip file to the system clipboard
+ local copy_cmd = vim.fn.has("mac") == 1
+ and string.format([[osascript -e 'set the clipboard to POSIX file "%s"']], zip_path)
+ or string.format([[echo -n %s | xclip -selection clipboard]], zip_path)
+ local copy_result = vim.fn.system(copy_cmd)
+ if vim.v.shell_error ~= 0 then
+ vim.notify("Failed to copy zip file to clipboard: " .. copy_result, vim.log.levels.ERROR)
+ return
+ end
+ vim.notify(zip_path, vim.log.levels.INFO)
+ vim.notify("Zipped and copied to clipboard: ", vim.log.levels.INFO)
+ else
+ vim.notify("No file or directory selected", vim.log.levels.WARN)
+ end
+ end
+
+ local paste_from_clipboard = function()
+ -- vim.notify("Starting the paste operation...", vim.log.levels.INFO)
+ if not mini_files then
+ vim.notify("mini.files module not loaded.", vim.log.levels.ERROR)
+ return
+ end
+ local curr_entry = mini_files.get_fs_entry() -- Get the current file system entry
+ if not curr_entry then
+ vim.notify("Failed to retrieve current entry in mini.files.", vim.log.levels.ERROR)
+ return
+ end
+ local curr_dir = curr_entry.fs_type == "directory" and curr_entry.path
+ or vim.fn.fnamemodify(curr_entry.path, ":h") -- Use parent directory if entry is a file
+ -- vim.notify("Current directory: " .. curr_dir, vim.log.levels.INFO)
+ local script = [[
+ tell application "System Events"
+ try
+ set theFile to the clipboard as alias
+ set posixPath to POSIX path of theFile
+ return posixPath
+ on error
+ return "error"
+ end try
+ end tell
+ ]]
+ local output = vim.fn.has("mac") == 1 and vim.fn.system("osascript -e " .. vim.fn.shellescape(script))
+ or vim.fn.system("xclip -o -selection clipboard")
+ if vim.v.shell_error ~= 0 or output:find("error") then
+ vim.notify("Clipboard does not contain a valid file or directory.", vim.log.levels.WARN)
+ return
+ end
+ local source_path = output:gsub("%s+$", "") -- Trim whitespace from clipboard output
+ if source_path == "" then
+ vim.notify("Clipboard is empty or invalid.", vim.log.levels.WARN)
+ return
+ end
+ local dest_path = curr_dir .. "/" .. vim.fn.fnamemodify(source_path, ":t") -- Destination path in current directory
+ local copy_cmd = vim.fn.isdirectory(source_path) == 1 and { "cp", "-R", source_path, dest_path }
+ or { "cp", source_path, dest_path } -- Construct copy command
+ local result = vim.fn.system(copy_cmd) -- Execute the copy command
+ if vim.v.shell_error ~= 0 then
+ vim.notify("Paste operation failed: " .. result, vim.log.levels.ERROR)
+ return
+ end
+ -- vim.notify("Pasted " .. source_path .. " to " .. dest_path, vim.log.levels.INFO)
+ mini_files.synchronize() -- Refresh mini.files to show updated directory content
+ vim.notify("Pasted successfully.", vim.log.levels.INFO)
+ end
+
+ local copy_path_to_clipboard = function()
+ -- Get the current entry (file or directory)
+ local curr_entry = mini_files.get_fs_entry()
+ if curr_entry then
+ -- Convert path to be relative to home directory
+ local home_dir = vim.fn.expand("~")
+ local relative_path = curr_entry.path:gsub("^" .. home_dir, "~")
+ vim.fn.setreg("+", relative_path) -- Copy the relative path to the clipboard register
+ vim.notify(vim.fn.fnamemodify(relative_path, ":t"), vim.log.levels.INFO)
+ vim.notify("Path copied to clipboard: ", vim.log.levels.INFO)
+ else
+ vim.notify("No file or directory selected", vim.log.levels.WARN)
+ end
+ end
+
+ local preview_image = function()
+ local curr_entry = mini_files.get_fs_entry()
+ if curr_entry then
+ -- Preview the file using Quick Look
+ if vim.fn.has("mac") == 1 then
+ vim.system({ "qlmanage", "-p", curr_entry.path }, {
+ stdout = false,
+ stderr = false,
+ })
+ vim.defer_fn(function()
+ vim.system({ "osascript", "-e", 'tell application "qlmanage" to activate' })
+ end, 200)
+ else
+ -- TODO: add previewer for linux
+ vim.notify("Preview not supported on Linux.", vim.log.levels.WARN)
+ end
+ else
+ vim.notify("No file selected", vim.log.levels.WARN)
+ end
+ end
+
+ local preview_image_popup = function()
+ -- Clear any existing images before rendering the new one
+ require("image").clear()
+ local curr_entry = mini_files.get_fs_entry()
+ if curr_entry and curr_entry.fs_type == "file" then
+ local ext = vim.fn.fnamemodify(curr_entry.path, ":e"):lower()
+ local supported_image_exts = { "png", "jpg", "jpeg", "gif", "bmp", "webp", "avif" }
+ -- Check if the file has a supported image extension
+ if vim.tbl_contains(supported_image_exts, ext) then
+ -- Save mini.files state (current path and focused entry)
+ local current_dir = vim.fn.fnamemodify(curr_entry.path, ":h")
+ local focused_entry = vim.fn.fnamemodify(curr_entry.path, ":t") -- Extract filename
+ -- Create a floating window for the image preview
+ local popup_width = math.floor(vim.o.columns * 0.6)
+ local popup_height = math.floor(vim.o.lines * 0.6)
+ local col = math.floor((vim.o.columns - popup_width) / 2)
+ local row = math.floor((vim.o.lines - popup_height) / 2)
+ local buf = vim.api.nvim_create_buf(false, true) -- Create a scratch buffer
+ local win = vim.api.nvim_open_win(buf, true, {
+ relative = "editor",
+ row = row,
+ col = col,
+ width = popup_width,
+ height = popup_height,
+ style = "minimal",
+ border = "rounded",
+ })
+ -- Declare img_width and img_height at the top
+ local img_width, img_height
+ -- Get image dimensions using ImageMagick's identify command
+ local dimensions = vim.fn.systemlist(
+ string.format("identify -format '%%w %%h' %s", vim.fn.shellescape(curr_entry.path))
+ )
+ if #dimensions > 0 then
+ img_width, img_height = dimensions[1]:match("(%d+) (%d+)")
+ img_width = tonumber(img_width)
+ img_height = tonumber(img_height)
+ end
+ -- Calculate image display size while maintaining aspect ratio
+ local display_width = popup_width
+ local display_height = popup_height
+ if img_width and img_height then
+ local aspect_ratio = img_width / img_height
+ if aspect_ratio > (popup_width / popup_height) then
+ -- Image is wider than the popup window
+ display_height = math.floor(popup_width / aspect_ratio)
+ else
+ -- Image is taller than the popup window
+ display_width = math.floor(popup_height * aspect_ratio)
+ end
+ end
+ -- Center the image within the popup window
+ local image_x = math.floor((popup_width - display_width) / 2)
+ local image_y = math.floor((popup_height - display_height) / 2)
+ -- Use image.nvim to render the image
+ local img = require("image").from_file(curr_entry.path, {
+ id = curr_entry.path, -- Unique ID
+ window = win, -- Bind the image to the popup window
+ buffer = buf, -- Bind the image to the popup buffer
+ x = image_x,
+ y = image_y,
+ width = display_width,
+ height = display_height,
+ with_virtual_padding = true,
+ })
+ -- Render the image
+ if img ~= nil then
+ img:render()
+ end
+ -- Use `stat` or `ls` to get the file size in bytes
+ local file_size_bytes = ""
+ if vim.fn.has("mac") == 1 or vim.fn.has("unix") == 1 then
+ -- For macOS or Linux systems
+ local handle = io.popen(
+ "stat -f%z "
+ .. vim.fn.shellescape(curr_entry.path)
+ .. " || ls -l "
+ .. vim.fn.shellescape(curr_entry.path)
+ .. " | awk '{print $5}'"
+ )
+ if handle then
+ file_size_bytes = handle:read("*a"):gsub("%s+$", "") -- Trim trailing whitespace
+ handle:close()
+ end
+ else
+ -- Fallback message if the command isn't available
+ file_size_bytes = "0"
+ end
+ -- Convert the size to MB (if valid)
+ local file_size_mb = tonumber(file_size_bytes) and tonumber(file_size_bytes) / (1024 * 1024)
+ or 0
+ local file_size_mb_str = string.format("%.2f", file_size_mb) -- Format to 2 decimal places as a string
+ -- Add image information (filename, size, resolution)
+ local image_info = {}
+ table.insert(image_info, "Image File: " .. focused_entry) -- Add only the filename
+ if tonumber(file_size_bytes) > 0 then
+ table.insert(image_info, "Size: " .. file_size_mb_str .. " MB") -- Use the formatted string
+ else
+ table.insert(image_info, "Size: Unable to detect") -- Fallback if size isn't found
+ end
+ if img_width and img_height then
+ table.insert(image_info, "Resolution: " .. img_width .. " x " .. img_height)
+ else
+ table.insert(image_info, "Resolution: Unable to detect")
+ end
+ -- Append the image information after the image
+ local line_count = vim.api.nvim_buf_line_count(buf)
+ vim.api.nvim_buf_set_lines(buf, line_count, -1, false, { "", "", "" }) -- Add 3 empty lines
+ vim.api.nvim_buf_set_lines(buf, -1, -1, false, image_info)
+ -- Keymap for closing the popup and reopening mini.files
+ local function reopen_mini_files()
+ if img ~= nil then
+ img:clear()
+ end
+ vim.api.nvim_win_close(win, true)
+ -- Reopen mini.files in the same directory
+ require("mini.files").open(current_dir, true)
+ vim.defer_fn(function()
+ -- Simulate navigation to the file by searching for the line matching the file
+ local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) -- Get all lines in the buffer
+ for i, line in ipairs(lines) do
+ if line:match(focused_entry) then
+ vim.api.nvim_win_set_cursor(0, { i, 0 }) -- Move cursor to the matching line
+ break
+ end
+ end
+ end, 50) -- Small delay to ensure mini.files is initialized
+ end
+ vim.keymap.set("n", "<esc>", reopen_mini_files, { buffer = buf, noremap = true, silent = true })
+ else
+ vim.notify("Not an image file.", vim.log.levels.WARN)
+ end
+ else
+ vim.notify("No file selected or not a file.", vim.log.levels.WARN)
+ end
+ end
+
+ local follow_symlink = function()
+ local curr_entry = mini_files.get_fs_entry()
+ if curr_entry and curr_entry.fs_type == "file" then
+ local resolved_path = vim.fn.resolve(curr_entry.path) -- Resolve symlink to original file
+ if resolved_path ~= curr_entry.path then
+ vim.notify("Following symlink to: " .. resolved_path, vim.log.levels.INFO)
+ mini_files.open(resolved_path, true) -- Open the original file in mini.files
+ else
+ vim.notify("The file is not a symlink.", vim.log.levels.WARN)
+ end
+ else
+ vim.notify("No file selected or not a valid file.", vim.log.levels.WARN)
+ end
+ end
+
+ vim.api.nvim_create_autocmd("User", {
+ pattern = "MiniFilesBufferCreate",
+ callback = function()
+ local buf_id = vim.api.nvim_get_current_buf()
+
+ vim.keymap.set(
+ "n",
+ opts.mappings and opts.mappings.toggle_hidden or "g.",
+ toggle_dotfiles,
+ { buffer = buf_id, desc = "Toggle hidden files" }
+ )
+
+ vim.keymap.set(
+ "n",
+ opts.mappings and opts.mappings.change_cwd or "gc",
+ files_set_cwd,
+ { buffer = buf_id, desc = "Set cwd" }
+ )
+
+ map_split(buf_id, opts.mappings and opts.mappings.go_in_horizontal or "<C-w>s", "horizontal", false)
+ map_split(buf_id, opts.mappings and opts.mappings.go_in_vertical or "<C-w>v", "vertical", false)
+ map_split(
+ buf_id,
+ opts.mappings and opts.mappings.go_in_horizontal_plus or "<C-w>S",
+ "horizontal",
+ true
+ )
+ map_split(buf_id, opts.mappings and opts.mappings.go_in_vertical_plus or "<C-w>V", "vertical", true)
+
+ vim.keymap.set(
+ "n",
+ "zt",
+ open_tmux_pane,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Open tmux pane" }
+ )
+ vim.keymap.set(
+ "n",
+ "zy",
+ copy_to_clipboard,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Copy to clipboard" }
+ )
+ vim.keymap.set(
+ "n",
+ "zY",
+ copy_path_to_clipboard,
+ { buffer = buf_id, desc = "Copy path to clipboard" }
+ )
+ vim.keymap.set(
+ "n",
+ "zc",
+ zip_and_copy_to_clipboard,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Zip and copy" }
+ )
+ vim.keymap.set(
+ "n",
+ "zp",
+ paste_from_clipboard,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Paste from clipboard" }
+ )
+ vim.keymap.set(
+ "n",
+ "zi",
+ preview_image,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Preview image" }
+ )
+ vim.keymap.set(
+ "n",
+ "zI",
+ preview_image_popup,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Pop-up preview image" }
+ )
+ vim.keymap.set(
+ "n",
+ "gl",
+ follow_symlink,
+ { buffer = buf_id, noremap = true, silent = true, desc = "Follow link" }
+ )
+ end,
+ })
+
+ -- Git status
+ local nsMiniFiles = vim.api.nvim_create_namespace("mini_files_git")
+ local autocmd = vim.api.nvim_create_autocmd
+ local _, MiniFiles = pcall(require, "mini.files")
+
+ -- Cache for git status
+ local gitStatusCache = {}
+ local cacheTimeout = 2000 -- Cache timeout in milliseconds
+
+ ---@type table<string, {symbol: string, hlGroup: string}>
+ ---@param status string
+ ---@return string symbol, string hlGroup
+ local function mapSymbols(status)
+ local statusMap = {
+ -- stylua: ignore start
+ [" M"] = { symbol = "•", hlGroup = "GitSignsChange"}, -- Modified in the working directory
+ ["M "] = { symbol = "✹", hlGroup = "GitSignsChange"}, -- modified in index
+ ["MM"] = { symbol = "≠", hlGroup = "GitSignsChange"}, -- modified in both working tree and index
+ ["A "] = { symbol = "+", hlGroup = "GitSignsAdd" }, -- Added to the staging area, new file
+ ["AA"] = { symbol = "≈", hlGroup = "GitSignsAdd" }, -- file is added in both working tree and index
+ ["D "] = { symbol = "-", hlGroup = "GitSignsDelete"}, -- Deleted from the staging area
+ ["AM"] = { symbol = "⊕", hlGroup = "GitSignsChange"}, -- added in working tree, modified in index
+ ["AD"] = { symbol = "-•", hlGroup = "GitSignsChange"}, -- Added in the index and deleted in the working directory
+ ["R "] = { symbol = "→", hlGroup = "GitSignsChange"}, -- Renamed in the index
+ ["U "] = { symbol = "‖", hlGroup = "GitSignsChange"}, -- Unmerged path
+ ["UU"] = { symbol = "⇄", hlGroup = "GitSignsAdd" }, -- file is unmerged
+ ["UA"] = { symbol = "⊕", hlGroup = "GitSignsAdd" }, -- file is unmerged and added in working tree
+ ["??"] = { symbol = "?", hlGroup = "GitSignsDelete"}, -- Untracked files
+ ["!!"] = { symbol = "!", hlGroup = "GitSignsChange"}, -- Ignored files
+ -- stylua: ignore end
+ }
+
+ local result = statusMap[status] or { symbol = "?", hlGroup = "NonText" }
+ return result.symbol, result.hlGroup
+ end
+
+ ---@param cwd string
+ ---@param callback function
+ ---@return nil
+ local function fetchGitStatus(cwd, callback)
+ local function on_exit(content)
+ if content.code == 0 then
+ callback(content.stdout)
+ vim.g.content = content.stdout
+ end
+ end
+ vim.system({ "git", "status", "--ignored", "--porcelain" }, { text = true, cwd = cwd }, on_exit)
+ end
+
+ ---@param str string?
+ local function escapePattern(str)
+ return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
+ end
+
+ ---@param buf_id integer
+ ---@param gitStatusMap table
+ ---@return nil
+ local function updateMiniWithGit(buf_id, gitStatusMap)
+ vim.schedule(function()
+ local nlines = vim.api.nvim_buf_line_count(buf_id)
+ local cwd = vim.fs.root(buf_id, ".git")
+ local escapedcwd = escapePattern(cwd)
+ if vim.fn.has("win32") == 1 then
+ escapedcwd = escapedcwd:gsub("\\", "/")
+ end
+
+ for i = 1, nlines do
+ local entry = MiniFiles.get_fs_entry(buf_id, i)
+ if not entry then
+ break
+ end
+ local relativePath = entry.path:gsub("^" .. escapedcwd .. "/", "")
+ local status = gitStatusMap[relativePath]
+
+ if status then
+ local symbol, hlGroup = mapSymbols(status)
+ vim.api.nvim_buf_set_extmark(buf_id, nsMiniFiles, i - 1, 0, {
+ -- NOTE: if you want the signs on the right uncomment those and comment
+ -- the 3 lines after
+ -- virt_text = { { symbol, hlGroup } },
+ -- virt_text_pos = "right_align",
+ sign_text = symbol,
+ sign_hl_group = hlGroup,
+ priority = 2,
+ })
+ else
+ end
+ end
+ end)
+ end
+
+ -- Thanks for the idea of gettings https://github.com/refractalize/oil-git-status.nvim signs for dirs
+ ---@param content string
+ ---@return table
+ local function parseGitStatus(content)
+ local gitStatusMap = {}
+ -- lua match is faster than vim.split (in my experience )
+ for line in content:gmatch("[^\r\n]+") do
+ local status, filePath = string.match(line, "^(..)%s+(.*)")
+ -- Split the file path into parts
+ local parts = {}
+ for part in filePath:gmatch("[^/]+") do
+ table.insert(parts, part)
+ end
+ -- Start with the root directory
+ local currentKey = ""
+ for i, part in ipairs(parts) do
+ if i > 1 then
+ -- Concatenate parts with a separator to create a unique key
+ currentKey = currentKey .. "/" .. part
+ else
+ currentKey = part
+ end
+ -- If it's the last part, it's a file, so add it with its status
+ if i == #parts then
+ gitStatusMap[currentKey] = status
+ else
+ -- If it's not the last part, it's a directory. Check if it exists, if not, add it.
+ if not gitStatusMap[currentKey] then
+ gitStatusMap[currentKey] = status
+ end
+ end
+ end
+ end
+ return gitStatusMap
+ end
+
+ ---@param buf_id integer
+ ---@return nil
+ local function updateGitStatus(buf_id)
+ if not vim.fs.root(vim.uv.cwd(), ".git") then
+ return
+ end
+
+ local cwd = vim.fn.expand("%:p:h")
+ local currentTime = os.time()
+ if gitStatusCache[cwd] and currentTime - gitStatusCache[cwd].time < cacheTimeout then
+ updateMiniWithGit(buf_id, gitStatusCache[cwd].statusMap)
+ else
+ fetchGitStatus(cwd, function(content)
+ local gitStatusMap = parseGitStatus(content)
+ gitStatusCache[cwd] = {
+ time = currentTime,
+ statusMap = gitStatusMap,
+ }
+ updateMiniWithGit(buf_id, gitStatusMap)
+ end)
+ end
+ end
+
+ ---@return nil
+ local function clearCache()
+ gitStatusCache = {}
+ end
+
+ local function augroup(name)
+ return vim.api.nvim_create_augroup("MiniFiles_" .. name, { clear = true })
+ end
+
+ autocmd("User", {
+ group = augroup("start"),
+ pattern = "MiniFilesExplorerOpen",
+ callback = function()
+ local bufnr = vim.api.nvim_get_current_buf()
+ updateGitStatus(bufnr)
+ end,
+ })
+
+ autocmd("User", {
+ group = augroup("close"),
+ pattern = "MiniFilesExplorerClose",
+ callback = function()
+ clearCache()
+ end,
+ })
+
+ autocmd("User", {
+ group = augroup("update"),
+ pattern = "MiniFilesBufferUpdate",
+ callback = function(sii)
+ local bufnr = sii.data.buf_id
+ local cwd = vim.fn.expand("%:p:h")
+ if gitStatusCache[cwd] then
+ updateMiniWithGit(bufnr, gitStatusCache[cwd].statusMap)
+ end
+ end,
+ })
+ end,
+ },
+ {
+ "echasnovski/mini.hipatterns",
+ version = false,
+ config = function()
+ local hipatterns = require("mini.hipatterns")
+ hipatterns.setup({
+ highlighters = {
+ -- Highlight standalone 'FIXME', 'HACK', 'TODO', 'NOTE'
+ fixme = { pattern = "%f[%w]()FIXME()%f[%W]", group = "MiniHipatternsFixme" },
+ hack = { pattern = "%f[%w]()HACK()%f[%W]", group = "MiniHipatternsHack" },
+ todo = { pattern = "%f[%w]()TODO()%f[%W]", group = "MiniHipatternsTodo" },
+ note = { pattern = "%f[%w]()NOTE()%f[%W]", group = "MiniHipatternsNote" },
+
+ -- Highlight hex color strings (`#rrggbb`) using that color
+ hex_color = hipatterns.gen_highlighter.hex_color(),
+ },
+ })
+ end,
+ },
+ {
+ "echasnovski/mini.indentscope",
+ version = false, -- wait till new 0.7.0 release to put it back on semver
+ event = "VeryLazy",
+ opts = {
+ mappings = {
+ -- Textobjects
+ object_scope = "i-",
+ object_scope_with_border = "a-",
+
+ -- Motions (jump to respective border line; if not present - body line)
+ goto_top = "g,",
+ goto_bottom = "g;",
+ },
+ draw = {
+ animation = function()
+ return 0
+ end,
+ },
+ options = { try_as_border = true },
+ symbol = "│",
+ },
+ init = function()
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = {
+ "help",
+ "Trouble",
+ "trouble",
+ "lazy",
+ "mason",
+ },
+ callback = function()
+ vim.b.miniindentscope_disable = true
+ end,
+ })
+ end,
+ },
+ {
+ "echasnovski/mini.map",
+ version = false,
+ config = function()
+ require("mini.map").setup(
+ -- No need to copy this inside `setup()`. Will be used automatically.
+ {
+ -- Highlight integrations (none by default)
+ integrations = nil,
+
+ -- Symbols used to display data
+ symbols = {
+ -- Encode symbols. See `:h MiniMap.config` for specification and
+ -- `:h MiniMap.gen_encode_symbols` for pre-built ones.
+ -- Default: solid blocks with 3x2 resolution.
+ encode = nil,
+
+ -- Scrollbar parts for view and line. Use empty string to disable any.
+ scroll_line = "█",
+ scroll_view = "┃",
+ },
+
+ -- Window options
+ window = {
+ -- Whether window is focusable in normal way (with `wincmd` or mouse)
+ focusable = true,
+
+ -- Side to stick ('left' or 'right')
+ side = "right",
+
+ -- Whether to show count of multiple integration highlights
+ show_integration_count = true,
+
+ -- Total width
+ width = 10,
+
+ -- Value of 'winblend' option
+ winblend = 25,
+
+ -- Z-index
+ zindex = 10,
+ },
+ }
+ )
+ end,
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>m", group = "Markdown/Map" },
+ { "<leader>mt", group = "Toggle" },
+ })
+ end,
+ keys = {
+ { "<leader>mo", "<cmd>lua MiniMap.open()<cr>", desc = "Open map" },
+ { "<leader>mr", "<cmd>lua MiniMap.refresh()<cr>", desc = "Refresh map" },
+ { "<leader>mc", "<cmd>lua MiniMap.close()<cr>", desc = "Close map" },
+ { "<leader>mtm", "<cmd>lua MiniMap.toggle()<cr>", desc = "Toggle map" },
+ { "<leader>mts", "<cmd>lua MiniMap.toggle_side()<cr>", desc = "Toggle side map" },
+ },
+ },
+ {
+ "echasnovski/mini.move",
+ version = false,
+ config = function()
+ -- No need to copy this inside `setup()`. Will be used automatically.
+ require("mini.move").setup({
+ -- Module mappings. Use `''` (empty string) to disable one.
+ mappings = {
+ -- Move visual selection in Visual mode. Defaults are Alt (Meta) + hjkl.
+ left = "<M-m>",
+ right = "<M-/>",
+ down = "<M-,>",
+ up = "<M-.>",
+
+ -- Move current line in Normal mode
+ line_left = "<M-m>",
+ line_right = "<M-/>",
+ line_down = "<M-,>",
+ line_up = "<M-.>",
+ },
+
+ -- Options which control moving behavior
+ options = {
+ -- Automatically reindent selection during linewise vertical move
+ reindent_linewise = true,
+ },
+ })
+ end,
+ },
+ {
+ "echasnovski/mini.pairs",
+ version = false,
+ event = "VeryLazy",
+ config = function()
+ require("mini.pairs").setup()
+ end,
+ keys = {
+ {
+ "<leader>zp",
+ function()
+ vim.g.minipairs_disable = not vim.g.minipairs_disable
+ end,
+ desc = "Toggle auto pairs",
+ },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/navic.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/navic.lua
new file mode 100644
index 0000000..89cfa81
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/navic.lua
@@ -0,0 +1,52 @@
+return {
+ "SmiteshP/nvim-navic",
+ dependencies = {
+ "neovim/nvim-lspconfig",
+ },
+ config = function()
+ require("nvim-navic").setup({
+ icons = {
+ File = "󰈙 ",
+ Module = " ",
+ Namespace = "󰌗 ",
+ Package = " ",
+ Class = "󰌗 ",
+ Method = "󰆧 ",
+ Property = " ",
+ Field = " ",
+ Constructor = " ",
+ Enum = "󰕘",
+ Interface = "󰕘",
+ Function = "󰊕 ",
+ Variable = "󰆧 ",
+ Constant = "󰏿 ",
+ String = "󰀬 ",
+ Number = "󰎠 ",
+ Boolean = "◩ ",
+ Array = "󰅪 ",
+ Object = "󰅩 ",
+ Key = "󰌋 ",
+ Null = "󰟢 ",
+ EnumMember = " ",
+ Struct = "󰌗 ",
+ Event = " ",
+ Operator = "󰆕 ",
+ TypeParameter = "󰊄 ",
+ },
+ lsp = {
+ auto_attach = true,
+ preference = nil,
+ },
+ highlight = true,
+ separator = " > ",
+ depth_limit = 5,
+ depth_limit_indicator = "..",
+ safe_output = true,
+ lazy_update_context = false,
+ click = true,
+ format_text = function(text)
+ return text
+ end,
+ })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/obsidian.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/obsidian.lua
new file mode 100644
index 0000000..ea8e893
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/obsidian.lua
@@ -0,0 +1,616 @@
+return {
+ "epwalsh/obsidian.nvim",
+ version = "*", -- recommended, use latest release instead of latest commit
+ lazy = true,
+ ft = "markdown",
+ -- Replace the above line with this if you only want to load obsidian.nvim for markdown files in your vault:
+ -- event = {
+ -- -- If you want to use the home shortcut '~' here you need to call 'vim.fn.expand'.
+ -- -- E.g. "BufReadPre " .. vim.fn.expand "~" .. "/my-vault/*.md"
+ -- -- refer to `:h file-pattern` for more examples
+ -- "BufReadPre path/to/my-vault/*.md",
+ -- "BufNewFile path/to/my-vault/*.md",
+ -- },
+ dependencies = {
+ -- Required.
+ "nvim-lua/plenary.nvim",
+ -- see below for full list of optional dependencies 👇
+ "hrsh7th/nvim-cmp",
+ "nvim-telescope/telescope.nvim",
+ "nvim-treesitter/nvim-treesitter",
+ {
+ "epwalsh/pomo.nvim",
+ dependencies = "nvim-lualine/lualine.nvim",
+ config = function()
+ require("lualine").setup({
+ sections = {
+ lualine_x = {
+ function()
+ local ok, pomo = pcall(require, "pomo")
+ if not ok then
+ return ""
+ end
+
+ local timer = pomo.get_first_to_finish()
+ if timer == nil then
+ return ""
+ end
+
+ return "󰄉 " .. tostring(timer)
+ end,
+ "encoding",
+ "fileformat",
+ "filetype",
+ },
+ },
+ })
+
+ require("telescope").load_extension("pomodori")
+
+ vim.keymap.set("n", "<leader>mp", function()
+ require("telescope").extensions.pomodori.timers()
+ end, { desc = "Manage pomodori" })
+ end,
+ },
+ },
+ cmd = {
+ "ObsidianOpen",
+ "ObsidianNew",
+ "ObsidianQuickSwitch",
+ "ObsidianFollowLink",
+ "ObsidianBacklinks",
+ "ObsidianTags",
+ "ObsidianToday",
+ "ObsidianYesterday",
+ "ObsidianTomorrow",
+ "ObsidianDailies",
+ "ObsidianTemplate",
+ "ObsidianSearch",
+ "ObsidianLink",
+ "ObsidianLinkNew",
+ "ObsidianExtractNote",
+ "ObsidianWorkspace",
+ "ObsidianPasteImg",
+ "ObsidianRename",
+ "ObsidianToggleCheckbox",
+ "ObsidianNewFromTemplate",
+ "ObsidianTOC",
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>o", group = "Open/Obsidian" },
+ { "<leader>of", group = "Find files (Obsidian)" },
+ { "<leader>on", group = "Notes (Obsidian)" },
+ { "<leader>op", group = "Paste (Obsidian)" },
+ { "<leader>ot", group = "Templates (Obsidian)" },
+ })
+ end,
+ config = function()
+ require("obsidian").setup({
+ -- A list of workspace names, paths, and configuration overrides.
+ -- If you use the Obsidian app, the 'path' of a workspace should generally be
+ -- your vault root (where the `.obsidian` folder is located).
+ -- When obsidian.nvim is loaded by your plugin manager, it will automatically set
+ -- the workspace to the first workspace in the list whose `path` is a parent of the
+ -- current markdown file being edited.
+ workspaces = {
+ {
+ name = "personal",
+ path = "~/Private/repos/Obsidian/SI",
+ -- Optional, override certain settings.
+ overrides = {
+ notes_subdir = "",
+ },
+ },
+ },
+
+ -- Alternatively - and for backwards compatibility - you can set 'dir' to a single path instead of
+ -- 'workspaces'. For example:
+ -- dir = "~/vaults/work",
+
+ -- Optional, if you keep notes in a specific subdirectory of your vault.
+ notes_subdir = "",
+
+ -- Optional, set the log level for obsidian.nvim. This is an integer corresponding to one of the log
+ -- levels defined by "vim.log.levels.*".
+ log_level = vim.log.levels.INFO,
+
+ daily_notes = {
+ -- Optional, if you keep daily notes in a separate directory.
+ folder = "Area/Notes",
+ -- Optional, if you want to change the date format for the ID of daily notes.
+ date_format = "%Y-%m-%d",
+ -- Optional, if you want to change the date format of the default alias of daily notes.
+ alias_format = "%B %-d, %Y",
+ -- Optional, default tags to add to each new daily note created.
+ default_tags = { "daily-notes" },
+ -- Optional, if you want to automatically insert a template from your template directory like 'daily.md'
+ template = nil,
+ },
+
+ -- Optional, completion of wiki links, local markdown links, and tags using nvim-cmp.
+ completion = {
+ -- Set to false to disable completion.
+ nvim_cmp = true,
+ -- Trigger completion at 2 chars.
+ min_chars = 2,
+ },
+
+ -- Optional, configure key mappings. These are the defaults. If you don't want to set any keymappings this
+ -- way then set 'mappings = {}'.
+ mappings = {
+ -- Smart action depending on context, either follow link or toggle checkbox.
+ ["<cr>"] = {
+ action = function()
+ return require("obsidian").util.smart_action()
+ end,
+ opts = { buffer = true, expr = true },
+ },
+ },
+
+ -- Where to put new notes. Valid options are
+ -- * "current_dir" - put new notes in same directory as the current buffer.
+ -- * "notes_subdir" - put new notes in the default notes subdirectory.
+ new_notes_location = "current_dir",
+
+ -- Optional, customize how note IDs are generated given an optional title.
+ ---@param title string|?
+ ---@return string
+ note_id_func = function(title)
+ -- Create note IDs in a Zettelkasten format with a timestamp and a suffix.
+ -- In this case a note with the title 'My new note' will be given an ID that looks
+ -- like '1657296016-my-new-note', and therefore the file name '1657296016-my-new-note.md'
+ local suffix = ""
+ if title ~= nil then
+ -- If title is given, transform it into valid file name.
+ suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
+ else
+ -- If title is nil, just add 4 random uppercase letters to the suffix.
+ for _ = 1, 4 do
+ suffix = suffix .. string.char(math.random(65, 90))
+ end
+ end
+ return suffix
+ end,
+
+ -- Optional, customize how note file names are generated given the ID, target directory, and title.
+ ---@param spec { id: string, dir: obsidian.Path, title: string|? }
+ ---@return string|obsidian.Path The full path to the new note.
+ note_path_func = function(spec)
+ -- This is equivalent to the default behavior.
+ local path = spec.dir / "Contents" / tostring(spec.title)
+ return path:with_suffix(".md")
+ end,
+
+ -- Optional, customize how wiki links are formatted. You can set this to one of:
+ -- * "use_alias_only", e.g. '[[Foo Bar]]'
+ -- * "prepend_note_id", e.g. '[[foo-bar|Foo Bar]]'
+ -- * "prepend_note_path", e.g. '[[foo-bar.md|Foo Bar]]'
+ -- * "use_path_only", e.g. '[[foo-bar.md]]'
+ -- Or you can set it to a function that takes a table of options and returns a string, like this:
+ wiki_link_func = function(opts)
+ return require("obsidian.util").wiki_link_path_prefix(opts)
+ end,
+
+ -- Optional, customize how markdown links are formatted.
+ markdown_link_func = function(opts)
+ return require("obsidian.util").markdown_link(opts)
+ end,
+
+ -- Either 'wiki' or 'markdown'.
+ preferred_link_style = "wiki",
+
+ -- Optional, boolean or a function that takes a filename and returns a boolean.
+ -- `true` indicates that you don't want obsidian.nvim to manage frontmatter.
+ disable_frontmatter = false,
+
+ -- -- Optional, alternatively you can customize the frontmatter data.
+ -- ---@return table
+ -- note_frontmatter_func = function(note)
+ -- -- Add the title of the note as an alias.
+ -- if note.title then
+ -- note:add_alias(note.title)
+ -- end
+ --
+ -- local out = { id = note.id, aliases = note.aliases, tags = note.tags }
+ --
+ -- -- `note.metadata` contains any manually added fields in the frontmatter.
+ -- -- So here we just make sure those fields are kept in the frontmatter.
+ -- if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then
+ -- for k, v in pairs(note.metadata) do
+ -- out[k] = v
+ -- end
+ -- end
+ --
+ -- return out
+ -- end,
+
+ -- Optional, for templates (see below).
+ templates = {
+ folder = "Resource/Templates",
+ date_format = "%Y-%m-%d",
+ time_format = "%H:%M",
+ -- A map for custom variables, the key should be the variable and the value a function
+ substitutions = {},
+ },
+
+ -- Optional, by default when you use `:ObsidianFollowLink` on a link to an external
+ -- URL it will be ignored but you can customize this behavior here.
+ ---@param url string
+ follow_url_func = function(url)
+ -- Open the URL in the default web browser.
+ -- vim.fn.jobstart({ "open", url }) -- Mac OS
+ vim.fn.jobstart({ "xdg-open", url }) -- linux
+ -- vim.cmd(':silent exec "!start ' .. url .. '"') -- Windows
+ -- vim.ui.open(url) -- need Neovim 0.10.0+
+ end,
+
+ -- Optional, by default when you use `:ObsidianFollowLink` on a link to an image
+ -- file it will be ignored but you can customize this behavior here.
+ ---@param img string
+ follow_img_func = function(img)
+ -- vim.fn.jobstart({ "qlmanage", "-p", img }) -- Mac OS quick look preview
+ vim.fn.jobstart({ "nsxiv", "-aiop", img }) -- linux
+ -- vim.cmd(':silent exec "!start ' .. url .. '"') -- Windows
+ end,
+
+ -- Optional, set to true if you use the Obsidian Advanced URI plugin.
+ -- https://github.com/Vinzent03/obsidian-advanced-uri
+ use_advanced_uri = false,
+
+ -- Optional, set to true to force ':ObsidianOpen' to bring the app to the foreground.
+ open_app_foreground = false,
+
+ picker = {
+ -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', or 'mini.pick'.
+ name = "telescope.nvim",
+ -- Optional, configure key mappings for the picker. These are the defaults.
+ -- Not all pickers support all mappings.
+ note_mappings = {
+ -- Create a new note from your query.
+ new = "<C-x>",
+ -- Insert a link to the selected note.
+ insert_link = "<C-l>",
+ },
+ tag_mappings = {
+ -- Add tag(s) to current note.
+ tag_note = "<C-x>",
+ -- Insert a tag at the current location.
+ insert_tag = "<C-l>",
+ },
+ },
+
+ -- Optional, sort search results by "path", "modified", "accessed", or "created".
+ -- The recommend value is "modified" and `true` for `sort_reversed`, which means, for example,
+ -- that `:ObsidianQuickSwitch` will show the notes sorted by latest modified time
+ sort_by = "modified",
+ sort_reversed = true,
+
+ -- Set the maximum number of lines to read from notes on disk when performing certain searches.
+ search_max_lines = 1000,
+
+ -- Optional, determines how certain commands open notes. The valid options are:
+ -- 1. "current" (the default) - to always open in the current window
+ -- 2. "vsplit" - to open in a vertical split if there's not already a vertical split
+ -- 3. "hsplit" - to open in a horizontal split if there's not already a horizontal split
+ open_notes_in = "current",
+
+ -- Optional, define your own callbacks to further customize behavior.
+ callbacks = {
+ -- Runs at the end of `require("obsidian").setup()`.
+ ---@param client obsidian.Client
+ post_setup = function(client) end,
+
+ -- Runs anytime you enter the buffer for a note.
+ ---@param client obsidian.Client
+ ---@param note obsidian.Note
+ enter_note = function(client, note) end,
+
+ -- Runs anytime you leave the buffer for a note.
+ ---@param client obsidian.Client
+ ---@param note obsidian.Note
+ leave_note = function(client, note) end,
+
+ -- Runs right before writing the buffer for a note.
+ ---@param client obsidian.Client
+ ---@param note obsidian.Note
+ pre_write_note = function(client, note) end,
+
+ -- Runs anytime the workspace is set/changed.
+ ---@param client obsidian.Client
+ ---@param workspace obsidian.Workspace
+ post_set_workspace = function(client, workspace) end,
+ },
+
+ -- Optional, configure additional syntax highlighting / extmarks.
+ -- This requires you have `conceallevel` set to 1 or 2. See `:help conceallevel` for more details.
+ ui = {
+ enable = false, -- set to false to disable all additional syntax features
+ update_debounce = 200, -- update delay after a text change (in milliseconds)
+ max_file_length = 5000, -- disable UI features for files with more than this many lines
+ -- Define how various check-boxes are displayed
+ checkboxes = {
+ -- NOTE: the 'char' value has to be a single character, and the highlight groups are defined below.
+ [" "] = { char = "󰄱", hl_group = "ObsidianTodo" },
+ ["x"] = { char = "", hl_group = "ObsidianDone" },
+ [">"] = { char = "", hl_group = "ObsidianRightArrow" },
+ ["~"] = { char = "󰰱", hl_group = "ObsidianTilde" },
+ ["!"] = { char = "", hl_group = "ObsidianImportant" },
+ -- Replace the above with this if you don't have a patched font:
+ -- [" "] = { char = "☐", hl_group = "ObsidianTodo" },
+ -- ["x"] = { char = "✔", hl_group = "ObsidianDone" },
+
+ -- You can also add more custom ones...
+ },
+ -- Use bullet marks for non-checkbox lists.
+ bullets = { char = "•", hl_group = "ObsidianBullet" },
+ external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
+ -- Replace the above with this if you don't have a patched font:
+ -- external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
+ reference_text = { hl_group = "ObsidianRefText" },
+ highlight_text = { hl_group = "ObsidianHighlightText" },
+ tags = { hl_group = "ObsidianTag" },
+ block_ids = { hl_group = "ObsidianBlockID" },
+ hl_groups = {
+ -- The options are passed directly to `vim.api.nvim_set_hl()`. See `:help nvim_set_hl`.
+ ObsidianTodo = { bold = true, fg = "#f78c6c" },
+ ObsidianDone = { bold = true, fg = "#89ddff" },
+ ObsidianRightArrow = { bold = true, fg = "#f78c6c" },
+ ObsidianTilde = { bold = true, fg = "#ff5370" },
+ ObsidianImportant = { bold = true, fg = "#d73128" },
+ ObsidianBullet = { bold = true, fg = "#89ddff" },
+ ObsidianRefText = { underline = true, fg = "#c792ea" },
+ ObsidianExtLinkIcon = { fg = "#c792ea" },
+ ObsidianTag = { italic = true, fg = "#89ddff" },
+ ObsidianBlockID = { italic = true, fg = "#89ddff" },
+ ObsidianHighlightText = { bg = "#75662e" },
+ },
+ },
+
+ -- Specify how to handle attachments.
+ attachments = {
+ -- The default folder to place images in via `:ObsidianPasteImg`.
+ -- If this is a relative path it will be interpreted as relative to the vault root.
+ -- You can always override this per image by passing a full path to the command instead of just a filename.
+ img_folder = "assets/imgs", -- This is the default
+
+ -- Optional, customize the default name or prefix when pasting images via `:ObsidianPasteImg`.
+ ---@return string
+ img_name_func = function()
+ -- Prefix image names with timestamp.
+ return string.format("%s-", os.time())
+ end,
+
+ -- A function that determines the text to insert in the note when pasting an image.
+ -- It takes two arguments, the `obsidian.Client` and an `obsidian.Path` to the image file.
+ -- This is the default implementation.
+ ---@param client obsidian.Client
+ ---@param path obsidian.Path the absolute path to the image file
+ ---@return string
+ img_text_func = function(client, path)
+ path = client:vault_relative_path(path) or path
+ return string.format("![%s](%s)", path.name, path)
+ end,
+ },
+ })
+
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = "markdown",
+ callback = function()
+ vim.keymap.set("n", "gl", function()
+ if require("obsidian").util.cursor_on_markdown_link() then
+ return "<cmd>ObsidianFollowLink<cr>"
+ else
+ return "gl"
+ end
+ end, { noremap = false, expr = true, desc = "Follow link (Obsidian)" })
+ end,
+ })
+ end,
+ keys = {
+ {
+ "<leader>zb",
+ function()
+ return require("obsidian").util.toggle_checkbox()
+ end,
+ buffer = true,
+ ft = "markdown",
+ desc = "Toggle check box (Obsidian)",
+ },
+ {
+ "<leader>ob",
+ function()
+ local query = vim.fn.input("Enter query: ")
+ if query and #query > 0 then
+ vim.cmd("ObsidianOpen " .. query)
+ end
+ end,
+ ft = "markdown",
+ desc = "Open note (Obsidian)",
+ },
+ {
+ "<leader>onn",
+ function()
+ local title = vim.fn.input("Enter title: ")
+ if title and #title > 0 then
+ vim.cmd("ObsidianNew " .. title)
+ end
+ end,
+ ft = "markdown",
+ desc = "New note (Obsidian)",
+ },
+ {
+ "<leader>os",
+ "<cmd>ObsidianQuickSwitch<cr>",
+ ft = "markdown",
+ desc = "Quick switch (Obsidian)",
+ },
+ {
+ "<leader>oL",
+ "<cmd>ObsidianFollowLink<cr>",
+ ft = "markdown",
+ desc = "Follow link (Obsidian)",
+ },
+ {
+ "<leader>oH",
+ "<cmd>ObsidianBacklinks<cr>",
+ ft = "markdown",
+ desc = "Back link (Obsidian)",
+ },
+ {
+ "<leader>oft",
+ function()
+ local tags = vim.fn.input("Enter tag: ")
+ if tags and #tags > 0 then
+ vim.cmd("ObsidianTags " .. tags)
+ end
+ end,
+ ft = "markdown",
+ desc = "Search tag notes (Obsidian)",
+ },
+ {
+ "<leader>ont",
+ function()
+ local offset = vim.fn.input("Enter offset: ")
+ if offset and #offset > 0 then
+ vim.cmd("ObsidianToday " .. offset)
+ else
+ vim.cmd("ObsidianToday")
+ end
+ end,
+ ft = "markdown",
+ desc = "Today note (Obsidian)",
+ },
+ {
+ "<leader>ony",
+ "<cmd>ObsidianYesterday<cr>",
+ ft = "markdown",
+ desc = "Yesterday note (Obsidian)",
+ },
+ {
+ "<leader>ont",
+ "<cmd>ObsidianTomorrow<cr>",
+ ft = "markdown",
+ desc = "Tomorrow note (Obsidian)",
+ },
+ {
+ "<leader>ond",
+ function()
+ local offset = vim.fn.input("Enter offset: ")
+ if offset and #offset > 0 then
+ vim.cmd("ObsidianDailies " .. offset)
+ else
+ vim.cmd("ObsidianDailies")
+ end
+ end,
+ ft = "markdown",
+ desc = "Daily notes (Obsidian)",
+ },
+ {
+ "<leader>oti",
+ "<cmd>ObsidianTemplate<cr>",
+ ft = "markdown",
+ desc = "Insert templates (Obsidian)",
+ },
+ {
+ "<leader>ofn",
+ function()
+ local note = vim.fn.input("Enter note: ")
+ if note and #note > 0 then
+ vim.cmd("ObsidianSearch " .. note)
+ end
+ end,
+ ft = "markdown",
+ desc = "Search note (Obsidian)",
+ },
+ {
+ "<leader>ow",
+ function()
+ local name = vim.fn.input("Enter name: ")
+ if name and #name > 0 then
+ vim.cmd("ObsidianWorkspace " .. name)
+ end
+ end,
+ ft = "markdown",
+ desc = "Workspace name (Obsidian)",
+ },
+ {
+ "<leader>opi",
+ function()
+ local image = vim.fn.input("Enter image: ")
+ if image and #image > 0 then
+ vim.cmd("ObsidianPasteImg " .. image)
+ end
+ end,
+ ft = "markdown",
+ desc = "Paste image (Obsidian)",
+ },
+ {
+ "<leader>onr",
+ function()
+ local name = vim.fn.input("Enter name: ")
+ if name and #name > 0 then
+ vim.cmd("ObsidianRename " .. name)
+ end
+ end,
+ ft = "markdown",
+ desc = "Rename note (Obsidian)",
+ },
+ {
+ mode = "v",
+ "<leader>ol",
+ function()
+ local query = vim.fn.input("Enter query: ")
+ if query and #query > 0 then
+ vim.cmd("ObsidianLink " .. query)
+ else
+ vim.cmd("ObsidianLink")
+ end
+ end,
+ ft = "markdown",
+ desc = "Link query (Obsidian)",
+ },
+ {
+ mode = "v",
+ "<leader>onl",
+ function()
+ local note = vim.fn.input("Enter note: ")
+ if note and #note > 0 then
+ vim.cmd("ObsidianLinkNew " .. note)
+ else
+ vim.cmd("ObsidianLinkNew")
+ end
+ end,
+ ft = "markdown",
+ desc = "New link note (Obsidian)",
+ },
+ {
+ mode = "v",
+ "<leader>ox",
+ function()
+ local note = vim.fn.input("Enter note: ")
+ if note and #note > 0 then
+ vim.cmd("ObsidianExtractNote " .. note)
+ else
+ vim.cmd("ObsidianExtractNote")
+ end
+ end,
+ ft = "markdown",
+ desc = "New extract text (Obsidian)",
+ },
+ {
+ "<leader>otn",
+ "<cmd>ObsidianNewFromTemplate<cr>",
+ ft = "markdown",
+ desc = "Open new note with template (Obsidian)",
+ },
+ {
+ "<leader>oc",
+ "<cmd>ObsidianTOC<cr>",
+ ft = "markdown",
+ desc = "Open contents (Obsidian)",
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/outline.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/outline.lua
new file mode 100644
index 0000000..e5b98f8
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/outline.lua
@@ -0,0 +1,11 @@
+return {
+ "hedyhli/outline.nvim",
+ lazy = true,
+ cmd = { "Outline", "OutlineOpen" },
+ keys = { -- Example mapping to toggle outline
+ { "<leader>zo", "<cmd>Outline<cr>", desc = "Toggle outline" },
+ },
+ opts = {
+ -- Your setup opts here
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/project.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/project.lua
new file mode 100644
index 0000000..a27afd4
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/project.lua
@@ -0,0 +1,12 @@
+return {
+ "ahmedkhalf/project.nvim",
+ dependencies = "nvim-telescope/telescope.nvim",
+ config = function()
+ require("project_nvim").setup()
+ require("telescope").load_extension("projects")
+
+ vim.keymap.set("n", "<leader>fpj", function()
+ require("telescope").extensions.projects.projects()
+ end, { desc = "Find projects" })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/python.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/python.lua
new file mode 100644
index 0000000..3e53ddb
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/python.lua
@@ -0,0 +1,59 @@
+return {
+ -- {
+ -- "bps/vim-textobj-python",
+ -- dependencies = { "kana/vim-textobj-user" },
+ -- },
+ {
+ "nvim-neotest/neotest",
+ optional = true,
+ dependencies = {
+ "nvim-neotest/neotest-python",
+ },
+ opts = {
+ adapters = {
+ ["neotest-python"] = {
+ -- Here you can specify the settings for the adapter, i.e.
+ -- runner = "pytest",
+ -- python = ".venv/bin/python",
+ },
+ },
+ },
+ },
+ {
+ "linux-cultist/venv-selector.nvim",
+ dependencies = {
+ "neovim/nvim-lspconfig",
+ "mfussenegger/nvim-dap",
+ "mfussenegger/nvim-dap-python", --optional
+ { "nvim-telescope/telescope.nvim", branch = "0.1.x", dependencies = { "nvim-lua/plenary.nvim" } },
+ },
+ lazy = false,
+ branch = "regexp", -- This is the regexp branch, use this for the new version
+ ft = "python",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>v", group = "Virtual envs" },
+ })
+ end,
+ config = function()
+ require("venv-selector").setup({
+ settings = {
+ options = {
+ notify_user_on_venv_activation = true,
+ },
+ search = {
+ venvs = {
+ command = "fd /bin/python$ ~/.local/share/venvs --full-path",
+ },
+ },
+ },
+ })
+ end,
+ keys = {
+ { "<leader>vs", "<cmd>VenvSelect<cr>", desc = "Select virtual env", ft = "python" },
+ { "<leader>vc", "<cmd>VenvSelectCached<cr>", desc = "Select venv (cache)", ft = "python" },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/quickfix.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/quickfix.lua
new file mode 100644
index 0000000..9a3998e
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/quickfix.lua
@@ -0,0 +1,76 @@
+return {
+ {
+ "folke/trouble.nvim",
+ cmd = { "Trouble" },
+ opts = {
+ modes = {
+ lsp = {
+ win = { position = "right" },
+ },
+ preview_float = {
+ mode = "diagnostics",
+ preview = {
+ type = "float",
+ relative = "editor",
+ border = "rounded",
+ title = "Preview",
+ title_pos = "center",
+ position = { 0, -2 },
+ size = { width = 0.3, height = 0.3 },
+ zindex = 200,
+ },
+ },
+ },
+ },
+ config = function(_, opts)
+ require("trouble").setup(opts)
+ end,
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>x", group = "Quickfix (trouble)" },
+ })
+ end,
+ keys = {
+ { "<leader>xd", "<cmd>Trouble diagnostics toggle<cr>", desc = "Toggle diagnostics (Trouble)" },
+ {
+ "<leader>xD",
+ "<cmd>Trouble diagnostics toggle filter.buf=0<cr>",
+ desc = "Toggle buffer Diagnostics (Trouble)",
+ },
+ { "<leader>xs", "<cmd>Trouble symbols toggle<cr>", desc = "Toggle symbols (Trouble)" },
+ { "<leader>xS", "<cmd>Trouble lsp toggle<cr>", desc = "Toggle LSP def/ref/... (Trouble)" },
+ { "<leader>xl", "<cmd>Trouble loclist toggle<cr>", desc = "Toggle location List (Trouble)" },
+ { "<leader>xq", "<cmd>Trouble qflist toggle<cr>", desc = "Toggle quickfix List (Trouble)" },
+ {
+ "[x",
+ function()
+ if require("trouble").is_open() then
+ require("trouble").prev({ skip_groups = true, jump = true })
+ else
+ local ok, err = pcall(vim.cmd.cprev)
+ if not ok then
+ vim.notify(err, vim.log.levels.ERROR)
+ end
+ end
+ end,
+ desc = "Previous quickfix (trouble)",
+ },
+ {
+ "]x",
+ function()
+ if require("trouble").is_open() then
+ require("trouble").next({ skip_groups = true, jump = true })
+ else
+ local ok, err = pcall(vim.cmd.cnext)
+ if not ok then
+ vim.notify(err, vim.log.levels.ERROR)
+ end
+ end
+ end,
+ desc = "Next quickfix (trouble)",
+ },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/refactoring.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/refactoring.lua
new file mode 100644
index 0000000..9c0603c
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/refactoring.lua
@@ -0,0 +1,60 @@
+return {
+ "ThePrimeagen/refactoring.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "nvim-treesitter/nvim-treesitter",
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v", "x" },
+ { "<leader>r", group = "Compiler/Refactoring" },
+ })
+ end,
+ config = function()
+ require("refactoring").setup({
+ prompt_func_return_type = {
+ c = true,
+ cpp = true,
+ cxx = true,
+ go = true,
+ h = true,
+ hpp = true,
+ java = true,
+ lua = true,
+ python = true,
+ },
+ prompt_func_param_type = {
+ c = true,
+ cpp = true,
+ cxx = true,
+ go = true,
+ h = true,
+ hpp = true,
+ java = true,
+ lua = true,
+ python = true,
+ },
+ printf_statements = {},
+ print_var_statements = {},
+ show_success_message = false,
+ })
+ vim.keymap.set("x", "<leader>re", ":Refactor extract ", { desc = "Extract" })
+ vim.keymap.set("x", "<leader>rf", ":Refactor extract_to_file ", { desc = "Extract to file" })
+ vim.keymap.set("x", "<leader>rv", ":Refactor extract_var ", { desc = "Extract variable" })
+ vim.keymap.set({ "n", "x" }, "<leader>ri", ":Refactor inline_var", { desc = "Refactor inline variable" })
+ vim.keymap.set("n", "<leader>rI", ":Refactor inline_func", { desc = "Refactor inline function" })
+ vim.keymap.set("n", "<leader>rb", ":Refactor extract_block", { desc = "Extract block" })
+ vim.keymap.set("n", "<leader>rbf", ":Refactor extract_block_to_file", { desc = "Extract block to file" })
+ -- prompt for a refactor to apply when the remap is triggered
+ vim.keymap.set({ "n", "x" }, "<leader>rs", function()
+ require("refactoring").select_refactor()
+ end, { desc = "Refactor selection" })
+ -- Note that not all refactor support both normal and visual mode
+ -- load refactoring Telescope extension
+ require("telescope").load_extension("refactoring")
+ vim.keymap.set({ "n", "x" }, "<leader>rf", function()
+ require("telescope").extensions.refactoring.refactors()
+ end, { desc = "Open refactor" })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/sessions.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/sessions.lua
new file mode 100644
index 0000000..c0be47b
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/sessions.lua
@@ -0,0 +1,32 @@
+return {
+ "folke/persistence.nvim",
+ event = "BufReadPre", -- this will only start session saving when an actual file was opened
+ config = function()
+ require("persistence").setup({
+ dir = vim.fn.stdpath("state") .. "/sessions/", -- directory where session files are saved
+ -- minimum number of file buffers that need to be open to save
+ -- Set to 0 to always save
+ need = 0,
+ branch = true, -- use git branch to save session
+ })
+
+ vim.keymap.set("n", "<leader>qs", function()
+ require("persistence").load()
+ end, { desc = "Load session" })
+
+ -- select a session to load
+ vim.keymap.set("n", "<leader>fs", function()
+ require("persistence").select()
+ end, { desc = "Find session" })
+
+ -- load the last session
+ vim.keymap.set("n", "<leader>ql", function()
+ require("persistence").load({ last = true })
+ end, { desc = "Last session" })
+
+ -- stop Persistence => session won't be saved on exit
+ vim.keymap.set("n", "<leader>qx", function()
+ require("persistence").stop()
+ end, { desc = "Stop session" })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/silicon.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/silicon.lua
new file mode 100644
index 0000000..ed63558
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/silicon.lua
@@ -0,0 +1,145 @@
+return {
+ "michaelrommel/nvim-silicon",
+ lazy = true,
+ cmd = "Silicon",
+ main = "nvim-silicon",
+ opts = {
+ -- Configuration here, or leave empty to use defaults
+ -- disable_defaults will disable all builtin default settings apart
+ -- from the base arguments, that are needed to call silicon at all, see
+ -- mandatory_options below, also those options can be overridden
+ -- all of the settings could be overridden in the lua setup call,
+ -- but this clashes with the use of an external silicon --config=file,
+ -- see issue #9
+ disable_defaults = false,
+ -- turn on debug messages
+ debug = false,
+ -- most of them could be overridden with other
+ -- the font settings with size and fallback font
+ -- Example: font = "VictorMono NF=34;Noto Emoji",
+ font = nil,
+ -- the theme to use, depends on themes available to silicon
+ theme = "gruvbox-dark",
+ -- the background color outside the rendered os window
+ -- (in hexcode string e.g "#076678")
+ background = nil,
+ -- a path to a background image
+ background_image = nil,
+ -- the paddings to either side
+ pad_horiz = 100,
+ pad_vert = 80,
+ -- whether to have the os window rendered with rounded corners
+ no_round_corner = false,
+ -- whether to put the close, minimize, maximise traffic light
+ -- controls on the border
+ no_window_controls = false,
+ -- whether to turn off the line numbers
+ no_line_number = false,
+ -- with which number the line numbering shall start
+ line_offset = 1,
+ -- here a function is used to return the actual source code line number
+ -- line_offset = function(args)
+ -- return args.line1
+ -- end,
+
+ -- the distance between lines of code
+ line_pad = 0,
+ -- the rendering of tab characters as so many space characters
+ tab_width = 4,
+ -- with which language the syntax highlighting shall be done, should be
+ -- a function that returns either a language name or an extension like "js"
+ -- it is set to nil, so you can override it, if you do not set it, we try the
+ -- filetype first, and if that fails, the extension
+ language = nil,
+ -- language = function()
+ -- return vim.bo.filetype
+ -- end,
+ -- language = function()
+ -- return vim.fn.fnamemodify(
+ -- vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()),
+ -- ":e"
+ -- )
+ -- end,
+
+ -- if the shadow below the os window should have be blurred
+ shadow_blur_radius = 16,
+ -- the offset of the shadow in x and y directions
+ shadow_offset_x = 8,
+ shadow_offset_y = 8,
+ -- the color of the shadow (in hexcode string e.g "#100808")
+ shadow_color = nil,
+ -- whether to strip of superfluous leading whitespace
+ gobble = true,
+ -- a string to pad each line with after gobbling removed larger indents,
+ num_separator = nil,
+ -- here a bar glyph is used to draw a vertial line and some space
+ -- num_separator = "\u{258f} ",
+
+ -- whether to put the image onto the clipboard, may produce an error,
+ -- if run on WSL2
+ to_clipboard = false,
+ -- a string or function returning a string that defines the title
+ -- showing in the image, only works in silicon versions greater than v0.5.1
+ window_title = nil,
+ -- here a function is used to get the name of the current buffer
+ -- window_title = function()
+ -- return vim.fn.fnamemodify(
+ -- vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()),
+ -- ":t"
+ -- )
+ -- end,
+
+ -- how to deal with the clipboard on WSL2
+ -- possible values are: never, always, auto
+ wslclipboard = nil,
+ -- what to do with the temporary screenshot image file when using the Windows
+ -- clipboard from WSL2, possible values are: keep, delete
+ wslclipboardcopy = nil,
+ -- the silicon command, put an absolute location here, if the
+ -- command is not in your ${PATH}
+ command = "silicon",
+ -- a string or function that defines the path to the output image
+ -- output = nil,
+ -- here a function is used to create a file in the current directory
+ output = function()
+ local home_dir = vim.fn.expand("~") -- Get home directory
+ local timestamp = os.date("!%Y-%m-%d_%H-%M-%S") -- Get timestamp
+ local file_name = vim.fn.expand("%:t:r") -- Get the file name without extension
+ local file_extension = vim.fn.expand("%:e")
+ return home_dir .. "/" .. timestamp .. "_" .. file_name .. "_" .. file_extension .. ".png"
+ end,
+ },
+ keys = {
+ {
+ mode = "v",
+ "<leader>sc",
+ function()
+ require("nvim-silicon").clip()
+ end,
+ desc = "Copy code screenshot to clipboard",
+ },
+ {
+ mode = "v",
+ "<leader>sf",
+ function()
+ require("nvim-silicon").file()
+ end,
+ desc = "Save code screenshot as file",
+ },
+ {
+ mode = "v",
+ "<leader>ss",
+ function()
+ require("nvim-silicon").shoot()
+ end,
+ desc = "Create code screenshot",
+ },
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "v" },
+ { "<leader>s", group = "Snapshot" },
+ })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/snippets.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/snippets.lua
new file mode 100644
index 0000000..eac9161
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/snippets.lua
@@ -0,0 +1,72 @@
+return {
+ {
+ "L3MON4D3/LuaSnip",
+ version = "v2.*",
+ build = "make install_jsregexp",
+ dependencies = {
+ "rafamadriz/friendly-snippets",
+ config = function()
+ require("luasnip.loaders.from_vscode").lazy_load()
+ end,
+ },
+ config = function()
+ local ls = require("luasnip")
+ ls.setup({
+ link_children = true,
+ link_roots = false,
+ keep_roots = false,
+ update_events = { "TextChanged", "TextChangedI" },
+ })
+ local c = ls.choice_node
+ ls.choice_node = function(pos, choices, opts)
+ if opts then
+ opts.restore_cursor = true
+ else
+ opts = { restore_cursor = true }
+ end
+ return c(pos, choices, opts)
+ end
+
+ require("luasnip.loaders.from_vscode").lazy_load({
+ paths = { '"' .. vim.fn.stdpath("config") .. '/lua/thesiahxyz/snippets"' },
+ })
+
+ vim.cmd.runtime({ args = { "lua/thesiahxyz/snippets/*.lua" }, bang = true }) -- load custom snippets
+
+ vim.keymap.set({ "i", "x" }, "<A-L>", function()
+ if ls.expand_or_jumpable() then
+ ls.expand_or_jump()
+ end
+ end, { silent = true, desc = "Expand snippet or jump to the next snippet node" })
+
+ vim.keymap.set({ "i", "x" }, "<A-H>", function()
+ if ls.jumpable(-1) then
+ ls.jump(-1)
+ end
+ end, { silent = true, desc = "Previous spot in the snippet" })
+
+ vim.keymap.set({ "i", "x" }, "<A-l>", function()
+ if ls.choice_active() then
+ ls.change_choice(1)
+ end
+ end, { silent = true, desc = "Next snippet choice" })
+
+ vim.keymap.set({ "i", "x" }, "<A-h>", function()
+ if ls.choice_active() then
+ ls.change_choice(-1)
+ end
+ end, { silent = true, desc = "Previous snippet choice" })
+ end,
+ keys = {
+ vim.keymap.set("i", "<tab>", function()
+ return require("luasnip").jumpable(1) and "<Plug>luasnip-jump-next" or "<tab>"
+ end, { expr = true, silent = true, desc = "Jump to next snippet" }),
+ vim.keymap.set("s", "<tab>", function()
+ require("luasnip").jump(1)
+ end, { desc = "Jump to next snippet" }),
+ vim.keymap.set({ "i", "s" }, "<s-tab>", function()
+ require("luasnip").jump(-1)
+ end, { desc = "Jump to Previous Snippet" }),
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/surround.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/surround.lua
new file mode 100644
index 0000000..f3e0174
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/surround.lua
@@ -0,0 +1,14 @@
+return {
+ "echasnovski/mini.surround",
+ version = "*",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "s", group = "Surround/Search & replace on line" },
+ })
+ end,
+ config = function()
+ require("mini.surround").setup()
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/telescope.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/telescope.lua
new file mode 100644
index 0000000..ceb6872
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/telescope.lua
@@ -0,0 +1,1041 @@
+local have_make = vim.fn.executable("make") == 1
+local have_cmake = vim.fn.executable("cmake") == 1
+
+function vim.live_grep_from_project_git_root()
+ local function get_git_toplevel()
+ local path = vim.fn.system("git rev-parse --show-toplevel")
+ if vim.v.shell_error then
+ return nil
+ end
+ return path
+ end
+
+ local opts = { cwd = get_git_toplevel() }
+
+ require("telescope.builtin").live_grep(opts)
+end
+
+local function find_nvim_plugin_files(prompt_bufnr)
+ local actions = require("telescope.actions")
+ local action_state = require("telescope.actions.state")
+
+ actions.close(prompt_bufnr)
+
+ local selection = action_state.get_selected_entry()
+ if selection and selection.value then
+ -- Construct the full path
+ local base_path = vim.fn.stdpath("data")
+ local full_path = vim.fn.resolve(base_path .. "/" .. selection.value)
+
+ require("mini.files").open(full_path, true)
+ end
+end
+
+return {
+ {
+ "nvim-telescope/telescope-file-browser.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "nvim-telescope/telescope.nvim",
+ },
+ -- init = function()
+ -- vim.api.nvim_create_autocmd("VimEnter", {
+ -- group = vim.api.nvim_create_augroup("TelescopeFileBrowserStartDirectory", { clear = true }),
+ -- desc = "Start telescope-file-browser with directory",
+ -- once = true,
+ -- callback = function()
+ -- if package.loaded["telescope-file-browser.nvim"] then
+ -- return
+ -- else
+ -- local stats = vim.uv.fs_stat(vim.fn.argv(0))
+ -- if stats and stats.type == "directory" then
+ -- require("telescope").extensions.file_browser.file_browser()
+ -- end
+ -- end
+ -- end,
+ -- })
+ -- end,
+ config = function()
+ local fb_actions = require("telescope._extensions.file_browser.actions")
+
+ require("telescope").setup({
+ extensions = {
+ file_browser = {
+ path = vim.uv.cwd(),
+ cwd = vim.uv.cwd(),
+ cwd_to_path = false,
+ grouped = true,
+ files = true,
+ add_dirs = true,
+ depth = 1,
+ auto_depth = true,
+ select_buffer = true,
+ hidden = { file_browser = false, folder_browser = false },
+ respect_gitignore = vim.fn.executable("fd") == 1,
+ no_ignore = true,
+ follow_symlinks = true,
+ browse_files = require("telescope._extensions.file_browser.finders").browse_files,
+ browse_folders = require("telescope._extensions.file_browser.finders").browse_folders,
+ hide_parent_dir = false,
+ collapse_dirs = true,
+ prompt_path = true,
+ quiet = true,
+ dir_icon = "",
+ dir_icon_hl = "Default",
+ display_stat = { date = true, size = true, mode = true },
+ hijack_netrw = false,
+ use_fd = true,
+ git_status = true,
+ mappings = {
+ ["i"] = {
+ ["<C-a>"] = fb_actions.create,
+ ["<C-e>"] = fb_actions.create_from_prompt,
+ ["<C-r>"] = fb_actions.rename,
+ ["<C-d>"] = fb_actions.move,
+ ["<C-y>"] = fb_actions.copy,
+ ["<Del>"] = fb_actions.remove,
+ ["<C-o>"] = fb_actions.open,
+ ["<C-h>"] = fb_actions.goto_parent_dir,
+ ["<C-Space>"] = fb_actions.goto_home_dir,
+ ["<C-w>"] = fb_actions.goto_cwd,
+ ["<C-g>"] = fb_actions.change_cwd,
+ ["<C-f>"] = fb_actions.toggle_browser,
+ ["<C-_>"] = fb_actions.toggle_hidden,
+ ["<C-t>"] = fb_actions.toggle_all,
+ ["<bs>"] = fb_actions.backspace,
+ },
+ ["n"] = {
+ ["a"] = fb_actions.create,
+ ["n"] = fb_actions.create_from_prompt,
+ ["r"] = fb_actions.rename,
+ ["d"] = fb_actions.move,
+ ["y"] = fb_actions.copy,
+ ["Del"] = fb_actions.remove,
+ ["o"] = fb_actions.open,
+ ["h"] = fb_actions.goto_parent_dir,
+ ["gh"] = fb_actions.goto_home_dir,
+ ["<C-w>"] = fb_actions.goto_cwd,
+ ["<C-g>"] = fb_actions.change_cwd,
+ ["f"] = fb_actions.toggle_browser,
+ ["/"] = fb_actions.toggle_hidden,
+ ["t"] = fb_actions.toggle_all,
+ },
+ },
+ results_title = vim.fn.fnamemodify(vim.uv.cwd(), ":~"),
+ },
+ },
+ })
+
+ require("telescope").load_extension("file_browser")
+
+ vim.keymap.set(
+ "n",
+ "<leader>et",
+ ":Telescope file_browser path=%:p:h select_buffer=true<CR>",
+ { desc = "File browser (cwd)" }
+ )
+ vim.keymap.set("n", "<leader>eT", ":Telescope file_browser<CR>", { desc = "File browser" })
+ end,
+ },
+ {
+ "nvim-telescope/telescope.nvim",
+ branch = "master",
+ dependencies = {
+ { "nvim-lua/plenary.nvim" },
+ {
+ "nvim-telescope/telescope-fzf-native.nvim",
+ build = have_make and "make"
+ or "cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build",
+ enabled = have_make or have_cmake,
+ config = function()
+ require("telescope").load_extension("fzf")
+ end,
+ },
+ {
+ "nvim-telescope/telescope-github.nvim",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<leader>gh", group = "gh" },
+ })
+ end,
+ config = function()
+ require("telescope").load_extension("gh")
+ vim.keymap.set({ "n", "v" }, "<leader>gi", ":Telescope gh issues ", { desc = "Find gh issues" })
+ vim.keymap.set(
+ { "n", "v" },
+ "<leader>gp",
+ ":Telescope gh pull_request ",
+ { desc = "Find gh pull request" }
+ )
+ vim.keymap.set({ "n", "v" }, "<leader>ght", ":Telescope gh gist ", { desc = "Find gh gist" })
+ vim.keymap.set({ "n", "v" }, "<leader>ghr", ":Telescope gh run ", { desc = "Find gh run" })
+ end,
+ },
+ {
+ "nvim-telescope/telescope-ui-select.nvim",
+ config = function()
+ require("telescope").setup({
+ extensions = {
+ ["ui-select"] = {
+ require("telescope.themes").get_dropdown({
+ -- even more opts
+ }),
+
+ -- pseudo code / specification for writing custom displays, like the one
+ -- for "codeactions"
+ -- specific_opts = {
+ -- make_indexed = function(items) -> indexed_items, width,
+ -- [kind] = {
+ -- make_displayer = function(widths) -> displayer
+ -- make_display = function(displayer) -> function(e)
+ -- make_ordinal = function(e) -> string
+ -- },
+ -- -- for example to disable the custom builtin "codeactions" display
+ -- do the following
+ -- codeactions = false,
+ -- }
+ },
+ },
+ })
+ require("telescope").load_extension("ui-select")
+ end,
+ },
+ {
+ "jvgrootveld/telescope-zoxide",
+ dependencies = { "nvim-lua/popup.nvim" },
+ config = function()
+ require("telescope").setup({
+ extensions = {
+ zoxide = {
+ prompt_title = "[ TheSiahxyz ]",
+ mappings = {
+ default = {
+ action = function(selection)
+ vim.cmd.cd(selection.path)
+ end,
+ after_action = function(selection)
+ print("Update to (" .. selection.z_score .. ") " .. selection.path)
+ end,
+ },
+ ["<C-s>"] = {
+ action = require("telescope._extensions.zoxide.utils").create_basic_command(
+ "split"
+ ),
+ opts = { desc = "split" },
+ },
+ ["<C-v>"] = {
+ action = require("telescope._extensions.zoxide.utils").create_basic_command(
+ "vsplit"
+ ),
+ },
+ ["<C-e>"] = {
+ action = require("telescope._extensions.zoxide.utils").create_basic_command(
+ "edit"
+ ),
+ },
+ ["<C-b>"] = {
+ keepinsert = true,
+ action = function(selection)
+ require("telescope").extensions.file_browser.file_browser({
+ cwd = selection.path,
+ })
+ end,
+ },
+ },
+ },
+ },
+ })
+ require("telescope").load_extension("zoxide")
+
+ vim.keymap.set("n", "<leader>fz", function()
+ require("telescope").extensions.zoxide.list()
+ end, { desc = "Find files (zoxide)" })
+ end,
+ },
+ {
+ "nvim-telescope/telescope-live-grep-args.nvim",
+ -- This will not install any breaking changes.
+ -- For major updates, this must be adjusted manually.
+ version = "^1.0.0",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>f", group = "Find" },
+ { "<leader>fl", group = "Live grep" },
+ })
+ end,
+ config = function()
+ local lga_actions = require("telescope-live-grep-args.actions")
+ local actions = require("telescope.actions")
+
+ require("telescope").setup({
+ extensions = {
+ live_grep_args = {
+ auto_quoting = true, -- enable/disable auto-quoting
+ -- define mappings, e.g.
+ mappings = { -- extend mappings
+ i = {
+ ["<C-w>"] = lga_actions.quote_prompt(),
+ ["<C-i>"] = lga_actions.quote_prompt({ postfix = " --iglob " }),
+ -- freeze the current list and start a fuzzy search in the frozen list
+ ["<C-space>"] = actions.to_fuzzy_refine,
+ },
+ },
+ vimgrep_arguments = {
+ "rg",
+ "--color=never",
+ "--no-heading",
+ "--with-filename",
+ "--line-number",
+ "--column",
+ "--smart-case",
+ "--follow",
+ "--hidden",
+ "--no-ignore",
+ },
+ -- ... also accepts theme settings, for example:
+ -- theme = "dropdown", -- use dropdown theme
+ -- theme = { }, -- use own theme spec
+ -- layout_config = { mirror=true }, -- mirror preview pane
+ },
+ },
+ })
+ require("telescope").load_extension("live_grep_args")
+ vim.keymap.set(
+ "n",
+ "<leader>flf",
+ ":lua require('telescope').extensions.live_grep_args.live_grep_args()<CR>",
+ { desc = "Find live grep args" }
+ )
+
+ local live_grep_args_shortcuts = require("telescope-live-grep-args.shortcuts")
+ vim.keymap.set(
+ "n",
+ "<leader>ss",
+ live_grep_args_shortcuts.grep_word_under_cursor,
+ { desc = "Search shortcuts (Live grep)" }
+ )
+
+ local function search_git(visual)
+ -- Retrieve the git root path
+ local handle = io.popen("git rev-parse --show-toplevel")
+ if not handle then
+ print("Error: Unable to open git handle")
+ return
+ end
+
+ local git_root_path = handle:read("*a"):gsub("%s+", "")
+ handle:close()
+
+ if not git_root_path or git_root_path == "" then
+ print("Error: Unable to retrieve git root path")
+ return
+ end
+
+ local opts = {
+ prompt_title = visual and ("Visual-Grep in " .. git_root_path)
+ or ("Live-Grep in " .. git_root_path),
+ shorten_path = false,
+ cwd = git_root_path,
+ file_ignore_patterns = { ".git", ".png", "tags" },
+ initial_mode = "insert",
+ selection_strategy = "reset",
+ theme = require("telescope.themes").get_dropdown({}),
+ }
+
+ if visual then
+ -- Capture the selected text in visual mode
+ vim.cmd('normal! "vy')
+ local visual_selection = vim.fn.getreg("v")
+ opts.search = visual_selection
+ require("telescope.builtin").grep_string(opts)
+ else
+ require("telescope.builtin").live_grep(opts)
+ end
+ end
+
+ vim.keymap.set("n", "<leader>flg", function()
+ search_git(false)
+ end, { remap = true, silent = false, desc = "Live grep in the git root folder" })
+
+ vim.keymap.set("v", "<leader>flg", function()
+ search_git(true)
+ end, { remap = true, silent = false, desc = "Grep in the git root folder" })
+ -- Retrieve the current tmux session path
+ -- This will not change when we navigate to a different pane
+ local function search_tmux(visual)
+ local handle = io.popen("tmux display-message -p '#{session_path}'")
+ if not handle then
+ print("Error: Unable to open tmux handle")
+ return
+ end
+
+ local tmux_session_path = handle:read("*a"):gsub("%s+", "")
+ handle:close()
+
+ if not tmux_session_path or tmux_session_path == "" then
+ print("Error: Unable to retrieve tmux session path")
+ return
+ end
+
+ local opts = {
+ prompt_title = visual and ("Visual-Grep in " .. tmux_session_path)
+ or ("Live-Grep in " .. tmux_session_path),
+ shorten_path = false,
+ cwd = tmux_session_path,
+ file_ignore_patterns = { ".git", ".png", "tags" },
+ initial_mode = "insert",
+ selection_strategy = "reset",
+ theme = require("telescope.themes").get_dropdown({}),
+ }
+
+ if visual then
+ require("telescope.builtin").grep_string(opts)
+ else
+ require("telescope.builtin").live_grep(opts)
+ end
+ end
+
+ vim.keymap.set("n", "<leader>flt", function()
+ search_tmux(false)
+ end, { remap = true, silent = false, desc = "Live grep in the current tmux session folder" })
+
+ vim.keymap.set("v", "<leader>flt", function()
+ search_tmux(true)
+ end, { remap = true, silent = false, desc = "Grep string in the current tmux session folder" })
+ vim.api.nvim_set_keymap(
+ "v",
+ "<leader>fls",
+ 'y<esc>:Telescope live_grep default_text=<c-r>0<cr> search_dirs={"$PWD"}',
+ { noremap = true, silent = true, desc = "Live grep default text" }
+ )
+ vim.keymap.set("n", "<leader>f/", function()
+ require("telescope.builtin").current_buffer_fuzzy_find(
+ require("telescope.themes").get_dropdown({
+ winblend = 10,
+ previewer = false,
+ relative = "editor",
+ })
+ )
+ end, { desc = "Find in current buffer" })
+ end,
+ },
+ {
+ "xiyaowong/telescope-emoji.nvim",
+ config = function()
+ require("telescope").setup({
+ extensions = {
+ emoji = {
+ action = function(emoji)
+ -- argument emoji is a table.
+ -- {name="", value="", cagegory="", description=""}
+
+ vim.fn.setreg("*", emoji.value)
+ print([[Press p or "*p to paste this emoji]] .. emoji.value)
+
+ -- insert emoji when picked
+ -- vim.api.nvim_put({ emoji.value }, 'c', false, true)
+ end,
+ },
+ },
+ })
+ require("telescope").load_extension("emoji")
+ end,
+ keys = {
+ { "<leader>se", ":Telescope emoji<cr>", desc = "Search emoji" },
+ },
+ },
+ {
+ "ThePrimeagen/harpoon",
+ branch = "harpoon2",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ },
+ {
+ "folke/trouble.nvim",
+ },
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<leader>f", group = "Find" },
+ { "<leader>fp", group = "Private/Public" },
+ { "<leader>s", group = "Search" },
+ { "<leader>sb", group = "Buffer" },
+ })
+ end,
+ config = function()
+ local actions = require("telescope.actions")
+ local actions_state = require("telescope.actions.state")
+ local actions_layout = require("telescope.actions.layout")
+ local open_with_trouble = require("trouble.sources.telescope").open
+ local add_to_trouble = require("trouble.sources.telescope").add
+
+ require("telescope").setup({
+ defaults = {
+ mappings = {
+ i = {
+ ["<C-a>"] = add_to_trouble,
+ ["<C-e>"] = actions.complete_tag,
+ ["<C-g>"] = function(prompt_bufnr)
+ local selection = actions_state.get_selected_entry()
+ local dir = vim.fn.fnamemodify(selection.path, ":p:h")
+ actions.close(prompt_bufnr)
+ -- Depending on what you want put `cd`, `lcd`, `tcd`
+ vim.cmd(string.format("silent lcd %s", dir))
+ end,
+ ["<C-u>"] = actions.nop,
+ ["<C-d>"] = actions.nop,
+ ["<C-b>"] = actions.nop,
+ ["<C-f>"] = actions_layout.toggle_preview,
+ ["<C-j>"] = actions.preview_scrolling_down,
+ ["<C-k>"] = actions.preview_scrolling_up,
+ ["<C-h>"] = actions.preview_scrolling_left,
+ ["<C-l>"] = actions.preview_scrolling_right,
+ ["<C-q>"] = actions.send_to_qflist + actions.open_qflist,
+ ["<M-q>"] = actions.send_selected_to_qflist + actions.open_qflist,
+ ["<C-t>"] = open_with_trouble,
+ ["<C-z>"] = actions.select_horizontal,
+ ["<C-w>"] = { "<c-s-w>", type = "command" },
+ ["<C-o><C-w>"] = actions.insert_original_cword,
+ ["<C-o><C-a>"] = actions.insert_original_cWORD,
+ ["<C-o><C-f>"] = actions.insert_original_cfile,
+ ["<C-o><C-l>"] = actions.insert_original_cline,
+ ["<M-f>"] = actions.nop,
+ ["<M-k>"] = actions.nop,
+ },
+ n = {
+ ["q"] = actions.close,
+ ["<C-a>"] = add_to_trouble,
+ ["<C-c>"] = actions.close,
+ ["<C-d>"] = actions.nop,
+ ["<C-u>"] = actions.nop,
+ ["<C-f>"] = actions_layout.toggle_preview,
+ ["<C-b>"] = actions.nop,
+ ["<C-e>"] = actions.complete_tag,
+ ["<C-g>"] = {
+ function(prompt_bufnr)
+ local selection = actions_state.get_selected_entry()
+ local dir = vim.fn.fnamemodify(selection.path, ":p:h")
+ actions.close(prompt_bufnr)
+ -- Depending on what you want put `cd`, `lcd`, `tcd`
+ vim.cmd(string.format("silent lcd %s", dir))
+ end,
+ opts = { desc = "Change directory" },
+ },
+ ["<C-j>"] = actions.preview_scrolling_down,
+ ["<C-k>"] = actions.preview_scrolling_up,
+ ["<C-h>"] = actions.preview_scrolling_left,
+ ["<C-l>"] = actions.preview_scrolling_right,
+ ["<C-q>"] = actions.send_to_qflist + actions.open_qflist,
+ ["<M-q>"] = actions.send_selected_to_qflist + actions.open_qflist,
+ ["<C-t>"] = open_with_trouble,
+ ["<C-z>"] = actions.select_horizontal,
+ ["<M-f"] = actions.nop,
+ ["<M-k"] = actions.nop,
+ },
+ },
+ file_ignore_patterns = {
+ "node_modules",
+ "yarn.lock",
+ ".git",
+ "^.git/",
+ "%/bin/zsh/",
+ ".sl",
+ "_build",
+ ".next",
+ "LICENSE",
+ "%lock%.json",
+ },
+ find_command = {
+ "rg",
+ "--files",
+ "--follow",
+ "--hidden",
+ "--no-ignore",
+ "--sortr=modified",
+ },
+ hidden = true,
+ path_display = {
+ "filename_first",
+ },
+ git_worktrees = {
+ {
+ home = vim.env.HOME,
+ private = vim.env.HOME .. "/Private/repos",
+ public = vim.env.HOME .. "/Public/repos",
+ },
+ },
+ results_title = vim.fn.fnamemodify(vim.uv.cwd(), ":~"),
+ scroll_strategy = "limit",
+ },
+ pickers = {
+ buffers = {
+ mappings = {
+ i = {
+ ["<C-x>"] = actions.delete_buffer,
+ },
+ n = {
+ ["dd"] = actions.delete_buffer,
+ ["<C-x>"] = actions.delete_buffer,
+ },
+ },
+ },
+ find_files = {
+ -- `hidden = true` will still show the inside of `.git/` as it's not `.gitignore`d.
+ find_command = {
+ "rg",
+ "--files",
+ "--follow",
+ "--hidden",
+ "--sortr=modified",
+ },
+ },
+ },
+ })
+
+ -- find
+ vim.keymap.set({ "i", "n" }, "<C-g>", function()
+ require("telescope.builtin").buffers({
+ sort_mru = true,
+ sort_lastused = true,
+ initial_mode = "normal",
+ })
+ end, { desc = "Find buffer files" })
+ vim.keymap.set("n", "<leader>fb", function()
+ require("telescope.builtin").buffers({
+ sort_mru = true,
+ sort_lastused = true,
+ initial_mode = "normal",
+ })
+ end, { desc = "Find buffer files" })
+ vim.keymap.set("n", "<leader>fc", function()
+ require("telescope.builtin").find_files({ cwd = vim.fn.expand("~/.config") })
+ end, { desc = "Find config files" })
+ vim.keymap.set("n", "<leader>fd", function()
+ require("telescope.builtin").find_files({ cwd = vim.fn.expand("~/.dotfiles") })
+ end, { desc = "Find dotfiles files" })
+ vim.keymap.set("n", "<leader>ff", function()
+ require("telescope.builtin").find_files()
+ end, { desc = "Find files" })
+ vim.keymap.set("n", "<leader>f<leader>", function()
+ local pickers = require("telescope.pickers")
+ local finders = require("telescope.finders")
+ local sorters = require("telescope.config").values.generic_sorter
+ local previewers = require("telescope.previewers")
+ local entry_display = require("telescope.pickers.entry_display")
+
+ -- Get list of open buffers
+ local opened_files = {}
+ for _, buf in ipairs(vim.api.nvim_list_bufs()) do
+ if vim.api.nvim_buf_is_loaded(buf) then
+ local name = vim.api.nvim_buf_get_name(buf)
+ if name ~= "" then
+ opened_files[name] = true
+ end
+ end
+ end
+
+ -- Create the custom picker
+ pickers
+ .new({}, {
+ prompt_title = "Find Files",
+ finder = finders.new_oneshot_job({ "fd", "--type", "f" }, {
+ entry_maker = function(entry)
+ local is_open = opened_files[vim.fn.fnamemodify(entry, ":p")] -- Match absolute paths
+ local displayer = entry_display.create({
+ separator = " ",
+ items = {
+ { width = 1 }, -- Marker width
+ { remaining = true }, -- Filepath
+ },
+ })
+
+ return {
+ value = entry,
+ ordinal = entry,
+ display = function()
+ return displayer({ is_open and "*" or " ", entry })
+ end,
+ path = entry,
+ }
+ end,
+ }),
+ sorter = sorters({}),
+ previewer = previewers.vim_buffer_cat.new({}),
+ })
+ :find()
+ end, { desc = "Find files with open markers" })
+
+ vim.keymap.set("n", "<leader>fF", function()
+ require("telescope.builtin").find_files({
+ cwd = vim.fn.expand("~"),
+ find_command = {
+ "rg",
+ "--files",
+ "--follow",
+ "--hidden",
+ "--glob",
+ "!**/.cache/*/*/*",
+ "--glob",
+ "!**/.mozilla/*",
+ "--glob",
+ "!**/.local/lib/*/*",
+ "--glob",
+ "!**/.local/share/*/*",
+ "--glob",
+ "!**/.local/state/*/*/*/*",
+ "--sortr=modified",
+ },
+ })
+ end, { desc = "Find root files" })
+ vim.keymap.set("n", "<leader>fk", function()
+ require("telescope.builtin").find_files({
+ cwd = vim.fn.expand("~/.local/src/suckless/"),
+ find_command = {
+ "find",
+ "-maxdepth",
+ "2",
+ "-type",
+ "f",
+ "-name",
+ ".git",
+ "-prune",
+ "-o",
+ },
+ })
+ end, { desc = "Find suckless files" })
+ vim.keymap.set("n", "<leader>fg", function()
+ require("telescope.builtin").git_files()
+ end, { desc = "Find git files" })
+ vim.keymap.set("n", "<leader>fo", function()
+ require("telescope.builtin").oldfiles({})
+ end, { desc = "Find old files" })
+ vim.keymap.set("n", "<leader>fpv", function()
+ require("telescope.builtin").find_files({ cwd = vim.fn.expand("~/Private") })
+ end, { desc = "Find private files" })
+ vim.keymap.set("n", "<leader>fpb", function()
+ require("telescope.builtin").find_files({ cwd = vim.fn.expand("~/Public") })
+ end, { desc = "Find public files" })
+ vim.keymap.set("n", "<leader>fr", function()
+ require("telescope.builtin").find_files({
+ cwd = vim.fn.expand("~/.local/bin"),
+ })
+ end, { desc = "Find script files" })
+ vim.keymap.set("n", "<leader>fv", function()
+ require("telescope.builtin").find_files({
+ cwd = vim.fn.stdpath("config"),
+ find_command = { "fd", "--type", "f", "--follow", "--color", "never", "--extension", "lua" },
+ })
+ end, { desc = "Find neovim config files" })
+ vim.keymap.set("n", "<leader>fV", function()
+ require("telescope.builtin").find_files({
+ cwd = vim.fn.stdpath("data"),
+ find_command = {
+ "find",
+ "-maxdepth",
+ "2",
+ "-type",
+ "d",
+ "-iname",
+ ".git",
+ "-prune",
+ "-o",
+ },
+ attach_mappings = function(_, map)
+ map("i", "<CR>", find_nvim_plugin_files)
+ map("n", "<CR>", find_nvim_plugin_files)
+ return true
+ end,
+ })
+ end, { desc = "Find neovim plugin files" })
+ -- git
+ vim.keymap.set("n", "<leader>gc", function()
+ require("telescope.builtin").git_commits()
+ end, { desc = "Find git commits" })
+ vim.keymap.set("n", "<leader>gC", function()
+ require("telescope.builtin").git_bcommits()
+ end, { desc = "Find buffer git commits" })
+ vim.keymap.set("n", "<leader>gb", function()
+ require("telescope.builtin").git_branches()
+ end, { desc = "Find branches" })
+ vim.keymap.set("n", "<leader>gl", function()
+ require("telescope.builtin").git_bcommits_range()
+ end, { desc = "Find lines git commits" })
+ vim.keymap.set("v", "<leader>gl", function()
+ require("telescope.builtin").git_bcommits_range()
+ end, { desc = "Find lines git commits" })
+ vim.keymap.set("n", "<leader>gs", function()
+ require("telescope.builtin").git_status()
+ end, { desc = "Find git status" })
+ vim.keymap.set("n", "<leader>gS", function()
+ require("telescope.builtin").git_stash()
+ end, { desc = "Find git stash" })
+ -- lsp
+ vim.keymap.set("n", "gd", function()
+ require("telescope.builtin").lsp_definitions({})
+ end, { desc = "Find definitions" })
+ vim.keymap.set("n", "gR", function()
+ require("telescope.builtin").lsp_references({})
+ end, { desc = "Find references" })
+ -- search
+ vim.keymap.set("n", "<leader>cc", function()
+ require("telescope.builtin").colorscheme({})
+ end, { desc = "Search color scheme" })
+ vim.keymap.set("n", "<leader>sa", function()
+ require("telescope.builtin").autocommands({})
+ end, { desc = "Search auto commands" })
+ vim.keymap.set("n", "<leader>sbf", function()
+ require("telescope.builtin").current_buffer_fuzzy_find({})
+ end, { desc = "Search current buffers " })
+ vim.keymap.set("n", "<leader>sbt", function()
+ require("telescope.builtin").current_buffer_tags({})
+ end, { desc = "Search current buffer tags" })
+ vim.keymap.set("n", "<leader>sc", function()
+ require("telescope.builtin").commands({})
+ end, { desc = "Search commands" })
+ vim.keymap.set("n", "<leader>sC", function()
+ require("telescope.builtin").command_history({})
+ end, { desc = "Search history" })
+ vim.keymap.set("n", "<leader>sd", function()
+ require("telescope.builtin").diagnostics({})
+ end, { desc = "Search diagonostics" })
+ vim.keymap.set("n", "<leader>sg", vim.live_grep_from_project_git_root, { desc = "Live grep (git)" })
+ vim.keymap.set("n", "<leader>sh", function()
+ require("telescope.builtin").help_tags({})
+ end, { desc = "Search help tags" })
+ vim.keymap.set("n", "<leader>sH", function()
+ require("telescope.builtin").highlights({})
+ end, { desc = "Search highlights" })
+ vim.keymap.set("n", "<leader>sj", function()
+ require("telescope.builtin").jumplist({})
+ end, { desc = "Search jump list" })
+ vim.keymap.set("n", "<leader>skb", function()
+ require("telescope.builtin").keymaps({})
+ end, { desc = "Search key bindings" })
+ vim.keymap.set("n", "<leader>skk", function()
+ local word = vim.fn.expand("<cword>")
+ require("telescope.builtin").grep_string({ search = word })
+ end, { desc = "Search words under cursor" })
+ vim.keymap.set("n", "<leader>skK", function()
+ local word = vim.fn.expand("<cWORD>")
+ require("telescope.builtin").grep_string({ search = word })
+ end, { desc = "Search all words under cursor" })
+ vim.keymap.set("n", "<leader>sl", function()
+ require("telescope.builtin").loclist({})
+ end, { desc = "Search location list" })
+ vim.keymap.set("n", "<leader>sm", function()
+ require("telescope.builtin").marks({})
+ end, { desc = "Search marks" })
+ vim.keymap.set("n", "<leader>sM", function()
+ require("telescope.builtin").man_pages({})
+ end, { desc = "Search man pages" })
+ vim.keymap.set("n", "<leader>so", function()
+ require("telescope.builtin").vim_options({})
+ end, { desc = "Search vim options" })
+ vim.keymap.set("n", "<leader>sq", function()
+ require("telescope.builtin").quickfix({})
+ end, { desc = "Search quickfix list" })
+ vim.keymap.set("n", "<leader>sQ", function()
+ require("telescope.builtin").quickfixhistory({})
+ end, { desc = "Search quickfix history" })
+ vim.keymap.set("n", "<leader>sr", function()
+ require("telescope.builtin").registers({})
+ end, { desc = "Search registers" })
+ vim.keymap.set("n", "<leader>sR", function()
+ require("telescope.builtin").resume({})
+ end, { desc = "Search resume" })
+ vim.keymap.set("n", "<leader>st", function()
+ require("telescope.builtin").filetypes({})
+ end, { desc = "Search file types" })
+ vim.keymap.set("n", "<leader>sw", function()
+ require("telescope.builtin").live_grep({})
+ end, { desc = "Search word (Live grep)" })
+ vim.keymap.set("n", "<leader>sW", function()
+ require("telescope.builtin").grep_string({})
+ end, { desc = "Search word (Grep)" })
+ end,
+ },
+ {
+ "cljoly/telescope-repo.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "nvim-telescope/telescope.nvim",
+ },
+ config = function()
+ require("telescope").setup({
+ extensions = {
+ repo = {
+ list = {
+ fd_opts = {
+ "--no-ignore-vcs",
+ },
+ file_ignore_patterns = {
+ "^" .. vim.env.HOME .. "/%.cache/",
+ "^" .. vim.env.HOME .. "/%.cargo/",
+ },
+ },
+ },
+ },
+ })
+
+ require("telescope").load_extension("repo")
+ end,
+ keys = {
+ { mode = "n", "<leader>fG", "<cmd>Telescope repo list<cr>", desc = "Find git files (repo)" },
+ },
+ },
+ {
+ "debugloop/telescope-undo.nvim",
+ dependencies = { -- note how they're inverted to above example
+ {
+ "nvim-telescope/telescope.nvim",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ },
+ },
+ opts = {
+ -- don't use `defaults = { }` here, do this in the main telescope spec
+ extensions = {
+ undo = {
+ -- telescope-undo.nvim config, see below
+ },
+ -- no other extensions here, they can have their own spec too
+ },
+ },
+ config = function(_, opts)
+ -- Calling telescope's setup from multiple specs does not hurt, it will happily merge the
+ -- configs for us. We won't use data, as everything is in it's own namespace (telescope
+ -- defaults, as well as each extension).
+ require("telescope").setup(opts)
+ require("telescope").load_extension("undo")
+ end,
+ keys = {
+ { -- lazy style key map
+ "<leader>fu",
+ "<cmd>Telescope undo<cr>",
+ desc = "Find undo history",
+ },
+ },
+ },
+ {
+ "nvim-telescope/telescope-frecency.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "nvim-telescope/telescope.nvim",
+ },
+ config = function()
+ require("telescope").setup({
+ extensions = {
+ frecency = {
+ auto_validate = false,
+ matcher = "fuzzy",
+ },
+ },
+ })
+
+ require("telescope").load_extension("frecency")
+
+ vim.keymap.set("n", "<leader>fq", function()
+ require("telescope").extensions.frecency.frecency({
+ workspace = "CWD",
+ })
+ end, { desc = "Find frequency files" })
+
+ vim.keymap.set("n", "<leader>flq", function()
+ local frecency = require("telescope").extensions.frecency
+ require("telescope.builtin").live_grep({
+ search_dirs = frecency.query({}),
+ })
+ end, { desc = "Find frequency live grep" })
+
+ vim.keymap.set("n", "<leader>qd", "<cmd>FrecencyDelete<cr>", { desc = "Delete current buffer frequency" })
+ end,
+ },
+ {
+ "nvim-telescope/telescope-media-files.nvim",
+ dependencies = {
+ "nvim-lua/popup.nvim",
+ "nvim-lua/plenary.nvim",
+ "nvim-telescope/telescope.nvim",
+ },
+ config = function()
+ require("telescope").setup({
+ extensions = {
+ media_files = {
+ -- filetypes whitelist
+ -- defaults to {"png", "jpg", "mp4", "webm", "pdf"}
+ filetypes = { "png", "jpg", "mp4", "mkv", "webm", "pdf" },
+ -- find command (defaults to `fd`)
+ find_cmd = "rg",
+ },
+ },
+ })
+ require("telescope").load_extension("media_files")
+ end,
+ keys = {
+ {
+ "<leader>fm",
+ "<cmd>Telescope media_files<cr>",
+ desc = "Find media files",
+ },
+ },
+ },
+ {
+ "nvim-telescope/telescope-project.nvim",
+ dependencies = {
+ "nvim-telescope/telescope.nvim",
+ "nvim-telescope/telescope-file-browser.nvim",
+ },
+ config = function()
+ local project_actions = require("telescope._extensions.project.actions")
+ require("telescope").setup({
+ extensions = {
+ project = {
+ base_dirs = {
+ { path = "~/Private", max_depth = 2 },
+ { path = "~/Public", max_depth = 2 },
+ },
+ mappings = {
+ i = {
+ ["<C-x>"] = project_actions.delete_project,
+ ["<C-r>"] = project_actions.rename_project,
+ ["<C-a>"] = project_actions.add_project,
+ ["<C-A>"] = project_actions.add_project_cwd,
+ ["<C-f>"] = project_actions.find_project_files,
+ ["<C-b>"] = project_actions.browse_project_files,
+ ["<C-s>"] = project_actions.search_in_project_files,
+ ["<C-o>"] = project_actions.recent_project_files,
+ ["<C-g>"] = project_actions.change_working_directory,
+ ["<C-l>"] = project_actions.next_cd_scope,
+ ["<C-w>"] = project_actions.change_workspace,
+ },
+ n = {
+ ["x"] = project_actions.delete_project,
+ ["r"] = project_actions.rename_project,
+ ["c"] = project_actions.add_project,
+ ["C"] = project_actions.add_project_cwd,
+ ["f"] = project_actions.find_project_files,
+ ["b"] = project_actions.browse_project_files,
+ ["s"] = project_actions.search_in_project_files,
+ ["o"] = project_actions.recent_project_files,
+ ["g"] = project_actions.change_working_directory,
+ ["l"] = project_actions.next_cd_scope,
+ },
+ },
+ },
+ },
+ })
+ require("telescope").load_extension("project")
+ end,
+ keys = {
+ {
+ "<leader>fpj",
+ "<cmd>Telescope projects<cr>",
+ desc = "Find projects",
+ },
+ },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/textobject.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/textobject.lua
new file mode 100644
index 0000000..3002fc0
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/textobject.lua
@@ -0,0 +1,140 @@
+return {
+ "nvim-treesitter/nvim-treesitter-textobjects",
+ dependencies = {
+ { "nvim-treesitter", build = ":TSUpdate" },
+ { "nvim-treesitter/nvim-treesitter" },
+ {
+ "chrisgrieser/nvim-various-textobjs",
+ event = "UIEnter",
+ opts = {
+ keymaps = {
+ useDefaults = true,
+ },
+ },
+ },
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ {
+ mode = { "n", "v", "x" },
+ { "g>", group = "Swap next" },
+ { "g<", group = "Swap prev" },
+ { "<leader>]", group = "Next" },
+ { "<leader>[", group = "Prev" },
+ },
+ })
+ end,
+ config = function()
+ require("nvim-treesitter.configs").setup({
+ textobjects = {
+ select = {
+ enable = true,
+
+ -- Automatically jump forward to textobj, similar to targets.vim
+ lookahead = true,
+
+ keymaps = {
+ -- You can use the capture groups defined in textobjects.scm
+ ["a="] = { query = "@assignment.outer", desc = "Select outer part of an assignment" },
+ ["i="] = { query = "@assignment.inner", desc = "Select inner part of an assignment" },
+ ["h="] = { query = "@assignment.lhs", desc = "Select left hand side of an assignment" },
+ ["l="] = { query = "@assignment.rhs", desc = "Select right hand side of an assignment" },
+
+ -- works for javascript/typescript files (custom capture I created in after/queries/ecma/textobjects.scm)
+ ["a:"] = { query = "@property.outer", desc = "Select outer part of an object property" },
+ ["i:"] = { query = "@property.inner", desc = "Select inner part of an object property" },
+ ["h:"] = { query = "@property.lhs", desc = "Select left part of an object property" },
+ ["l:"] = { query = "@property.rhs", desc = "Select right part of an object property" },
+
+ ["aa"] = { query = "@parameter.outer", desc = "Select outer part of a parameter/argument" },
+ ["ia"] = { query = "@parameter.inner", desc = "Select inner part of a parameter/argument" },
+
+ ["an"] = { query = "@conditional.outer", desc = "Select outer part of a conditional" },
+ ["in"] = { query = "@conditional.inner", desc = "Select inner part of a conditional" },
+
+ ["ap"] = { query = "@loop.outer", desc = "Select outer part of a loop" },
+ ["ip"] = { query = "@loop.inner", desc = "Select inner part of a loop" },
+
+ ["af"] = { query = "@call.outer", desc = "Select outer part of a function call" },
+ ["if"] = { query = "@call.inner", desc = "Select inner part of a function call" },
+
+ ["am"] = {
+ query = "@function.outer",
+ desc = "Select outer part of a method/function definition",
+ },
+ ["im"] = {
+ query = "@function.inner",
+ desc = "Select inner part of a method/function definition",
+ },
+
+ ["ac"] = { query = "@class.outer", desc = "Select outer part of a class" },
+ ["ic"] = { query = "@class.inner", desc = "Select inner part of a class" },
+ },
+ },
+ swap = {
+ enable = true,
+ swap_next = {
+ ["g>a"] = { query = "@parameter.inner", desc = "swap parameters/argument with next" },
+ ["g>:"] = { query = "@property.outer", desc = "swap object property with next" },
+ ["g>m"] = { query = "@function.outer", desc = "swap function with next" },
+ },
+ swap_previous = {
+ ["g<a"] = { query = "@parameter.inner", desc = "swap parameters/argument with prev" },
+ ["g<:"] = { query = "@property.outer", desc = "swap object property with prev" },
+ ["g<m"] = { query = "@function.outer", desc = "swap function with previous" },
+ },
+ },
+ move = {
+ enable = true,
+ set_jumps = true, -- whether to set jumps in the jumplist
+ goto_next_start = {
+ ["]f"] = { query = "@call.outer", desc = "Next function call start" },
+ ["]m"] = { query = "@function.outer", desc = "Next method/function def start" },
+ ["]c"] = { query = "@class.outer", desc = "Next class start" },
+ ["]="] = { query = "@conditional.outer", desc = "Next conditional start" },
+ ["]l"] = { query = "@loop.outer", desc = "Next loop start" },
+
+ -- You can pass a query group to use query from `queries/<lang>/<query_group>.scm file in your runtime path.
+ -- Below example nvim-treesitter's `locals.scm` and `folds.scm`. They also provide highlights.scm and indent.scm.
+ ["]-"] = { query = "@scope", query_group = "locals", desc = "Next scope" },
+ ["]z"] = { query = "@fold", query_group = "folds", desc = "Next fold" },
+ },
+ goto_next_end = {
+ ["]F"] = { query = "@call.outer", desc = "Next function call end" },
+ ["]M"] = { query = "@function.outer", desc = "Next method/function def end" },
+ ["]C"] = { query = "@class.outer", desc = "Next class end" },
+ ["]+"] = { query = "@conditional.outer", desc = "Next conditional end" },
+ ["]L"] = { query = "@loop.outer", desc = "Next loop end" },
+ },
+ goto_previous_start = {
+ ["[f"] = { query = "@call.outer", desc = "Prev function call start" },
+ ["[m"] = { query = "@function.outer", desc = "Prev method/function def start" },
+ ["[c"] = { query = "@class.outer", desc = "Prev class start" },
+ ["[="] = { query = "@conditional.outer", desc = "Prev conditional start" },
+ ["[l"] = { query = "@loop.outer", desc = "Prev loop start" },
+ },
+ goto_previous_end = {
+ ["[F"] = { query = "@call.outer", desc = "Prev function call end" },
+ ["[M"] = { query = "@function.outer", desc = "Prev method/function def end" },
+ ["[C"] = { query = "@class.outer", desc = "Prev class end" },
+ ["[+"] = { query = "@conditional.outer", desc = "Prev conditional end" },
+ ["[L"] = { query = "@loop.outer", desc = "Prev loop end" },
+ },
+ },
+ },
+ })
+
+ local ts_repeat_move = require("nvim-treesitter.textobjects.repeatable_move")
+
+ -- vim way: ; goes to the direction you were moving.
+ vim.keymap.set({ "n", "x", "o" }, ";", ts_repeat_move.repeat_last_move)
+ vim.keymap.set({ "n", "x", "o" }, ",", ts_repeat_move.repeat_last_move_opposite)
+
+ -- Optionally, make builtin f, F, t, T also repeatable with ; and ,
+ vim.keymap.set({ "n", "x", "o" }, "f", ts_repeat_move.builtin_f_expr, { expr = true })
+ vim.keymap.set({ "n", "x", "o" }, "F", ts_repeat_move.builtin_F_expr, { expr = true })
+ vim.keymap.set({ "n", "x", "o" }, "t", ts_repeat_move.builtin_t_expr, { expr = true })
+ vim.keymap.set({ "n", "x", "o" }, "T", ts_repeat_move.builtin_T_expr, { expr = true })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/treesitter.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/treesitter.lua
new file mode 100644
index 0000000..c486343
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/treesitter.lua
@@ -0,0 +1,62 @@
+return {
+ "nvim-treesitter/nvim-treesitter",
+ build = ":TSUpdate",
+ dependencies = { "nvim-treesitter/nvim-treesitter-textobjects" },
+ config = function()
+ require("nvim-treesitter.configs").setup({
+ -- A list of parser names, or "all"
+ ensure_installed = {
+ "bash",
+ "c",
+ "cpp",
+ "dockerfile",
+ "html",
+ "java",
+ "json5",
+ "latex",
+ "lua",
+ "markdown",
+ "markdown_inline",
+ "python",
+ "sql",
+ "vim",
+ "vimdoc",
+ },
+
+ -- Install parsers synchronously (only applied to `ensure_installed`)
+ sync_install = true,
+
+ -- Automatically install missing parsers when entering buffer
+ -- Recommendation: set to false if you don"t have `tree-sitter` CLI installed locally
+ auto_install = true,
+
+ -- List of parsers to ignore installing (or "all")
+ ignore_install = {},
+
+ highlight = {
+ -- `false` will disable the whole extension
+ enable = true,
+ disable = { "csv" },
+ -- Setting this to true will run `:h syntax` and tree-sitter at the same time.
+ -- Set this to `true` if you depend on "syntax" being enabled (like for indentation).
+ -- Using this option may slow down your editor, and you may see some duplicate highlights.
+ -- Instead of true it can also be a list of languages
+ additional_vim_regex_highlighting = { "markdown" },
+ },
+ })
+
+ local treesitter_parser_config = require("nvim-treesitter.parsers").get_parser_configs()
+ treesitter_parser_config.templ = {
+ install_info = {
+ url = "https://github.com/vrischmann/tree-sitter-templ.git",
+ files = { "src/parser.c", "src/scanner.c" },
+ branch = "master",
+ },
+ }
+
+ vim.treesitter.language.register("templ", "templ")
+ end,
+ keys = {
+ { "<leader>T", ":TSUpdate<cr>", desc = "Update treesitter" },
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ufo.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ufo.lua
new file mode 100644
index 0000000..acce8d0
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/ufo.lua
@@ -0,0 +1,56 @@
+return {
+ "kevinhwang91/nvim-ufo",
+ dependencies = {
+ "kevinhwang91/promise-async",
+ },
+ config = function()
+ vim.o.foldcolumn = "0" -- '0' is not bad
+ vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value
+ vim.o.foldlevelstart = 99
+ vim.o.foldenable = true
+
+ -- Using ufo provider need remap `zR` and `zM`. If Neovim is 0.6.1, remap yourself
+ vim.keymap.set("n", "zR", require("ufo").openAllFolds)
+ vim.keymap.set("n", "zM", require("ufo").closeAllFolds)
+
+ -- Option 1: coc.nvim as LSP client
+ -- { "neoclide/coc.nvim", branch = "master", run = "yarn install --frozen-lockfile" }
+ -- require("ufo").setup()
+
+ -- Option 2: nvim lsp as LSP client
+ -- Tell the server the capability of foldingRange,
+ -- Neovim hasn't added foldingRange to default capabilities, users must add it manually
+ local capabilities = vim.lsp.protocol.make_client_capabilities()
+ capabilities.textDocument.foldingRange = {
+ dynamicRegistration = false,
+ lineFoldingOnly = true,
+ }
+ local language_servers = require("lspconfig").util._available_servers() -- or list servers manually like {'gopls', 'clangd'}
+ for _, ls in ipairs(language_servers) do
+ require("lspconfig")[ls].setup({
+ capabilities = capabilities,
+ -- you can add other fields for setting up lsp server in this table
+ })
+ end
+ require("ufo").setup()
+
+ -- Option 3: treesitter as a main provider instead
+ -- (Note: the `nvim-treesitter` plugin is *not* needed.)
+ -- ufo uses the same query files for folding (queries/<lang>/folds.scm)
+ -- performance and stability are better than `foldmethod=nvim_treesitter#foldexpr()`
+ -- require("ufo").setup({
+ -- provider_selector = function(bufnr, filetype, buftype)
+ -- return { "treesitter", "indent" }
+ -- end,
+ -- })
+ --
+
+ -- Option 4: disable all providers for all buffers
+ -- Not recommend, AFAIK, the ufo's providers are the best performance in Neovim
+ -- require("ufo").setup({
+ -- provider_selector = function(bufnr, filetype, buftype)
+ -- return ""
+ -- end,
+ -- })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/urlview.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/urlview.lua
new file mode 100644
index 0000000..c1a7108
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/urlview.lua
@@ -0,0 +1,103 @@
+return {
+ "axieax/urlview.nvim",
+ dependencies = "nvim-telescope/telescope.nvim",
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n", "v" },
+ { "<leader>u", group = "URLs" },
+ { "<leader>uc", group = "Copy URLs" },
+ })
+ end,
+ config = function()
+ -- Load urlview
+ require("urlview").setup({})
+
+ -- Define custom search for thesiah_urls
+ local thesiah = require("urlview.search")
+ thesiah["thesiah_urls"] = function()
+ local urls = {}
+ local files = {
+ vim.fn.expand("~/.local/share/thesiah/urls"),
+ vim.fn.expand("~/.local/share/thesiah/snippets"),
+ }
+
+ -- Check if the file exists and read each file
+ for _, filepath in ipairs(files) do
+ if vim.fn.filereadable(filepath) == 1 then
+ local file = io.open(filepath, "r")
+ if file then
+ for line in file:lines() do
+ -- Match and capture URLs
+ for url in line:gmatch("https?://[%w%./%-_%%]+") do
+ table.insert(urls, url)
+ end
+ end
+ file:close()
+ else
+ vim.notify("Unable to open " .. filepath, vim.log.levels.ERROR)
+ end
+ else
+ vim.notify("File not found: " .. filepath, vim.log.levels.WARN)
+ end
+ end
+
+ return urls
+ end
+
+ local search = require("urlview.search")
+ local search_helpers = require("urlview.search.helpers")
+
+ -- Custom search function for Tmux plugins
+ search["tmux_plugins"] = function()
+ local urls = {}
+ local filepath = vim.fn.expand("~/.config/tmux/tmux.conf")
+
+ -- Check if the tmux.conf file exists
+ if vim.fn.filereadable(filepath) == 1 then
+ local file = io.open(filepath, "r")
+ if file then
+ for line in file:lines() do
+ -- Match lines that contain Tmux plugin URLs (TPM syntax)
+ -- Example: set -g @plugin 'tmux-plugins/tpm'
+ local url = line:match("@plugin%s+'([^']+)'")
+ if url then
+ -- Convert to full GitHub URL if not already a full URL
+ if not url:match("^https?://") then
+ url = "https://github.com/" .. url
+ end
+ table.insert(urls, url)
+ end
+ end
+ file:close()
+ else
+ vim.notify("Unable to open " .. filepath, vim.log.levels.ERROR)
+ end
+ else
+ vim.notify("File not found: " .. filepath, vim.log.levels.WARN)
+ end
+
+ return urls
+ end
+
+ -- Add a keymap for the Tmux plugins search context
+ vim.keymap.set("n", "<leader>ub", "<cmd>UrlView thesiah_urls<cr>", { desc = "Bookmarks URLs" })
+ vim.keymap.set("n", "<leader>ul", "<cmd>UrlView lazy<cr>", { desc = "Lazy plugin URLs" })
+ vim.keymap.set("n", "<leader>ur", "<cmd>UrlView<cr>", { desc = "Buffer URLs" })
+ vim.keymap.set("n", "<leader>ut", "<cmd>UrlView tmux_plugins<cr>", { desc = "Tmux plugin URLs" })
+ vim.keymap.set(
+ "n",
+ "<leader>ucb",
+ "<cmd>UrlView thesiah_urls action=clipboard<cr>",
+ { desc = "clipboard bookmarks URLs" }
+ )
+ vim.keymap.set("n", "<leader>ucl", "<cmd>UrlView lazy action=clipboard<cr>", { desc = "Copy Lazy plugin URLs" })
+ vim.keymap.set("n", "<leader>ucr", "<cmd>UrlView action=clipboard<cr>", { desc = "Copy buffer URLs" })
+ vim.keymap.set(
+ "n",
+ "<leader>uct",
+ "<cmd>UrlView tmux_plugins action=clipboard<cr>",
+ { desc = "clipboard tmux plugin URLs" }
+ )
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/vimwiki.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/vimwiki.lua
new file mode 100644
index 0000000..73ed91c
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/vimwiki.lua
@@ -0,0 +1,64 @@
+return {
+ {
+ "vimwiki/vimwiki",
+ cmd = {
+ "VimwikiIndex",
+ "VimwikiDeleteFile",
+ "Vimwiki2HTML",
+ "VimwikiAll2HTML",
+ "Vimwiki2HTMLBrowse",
+ "VimwikiGoto",
+ "VimwikiRenameFile",
+ "VimwikiSplitLink",
+ "VimwikiVSplitLink",
+ "VimwikiColorize",
+ "VimwikiDiaryGenerateLinks",
+ },
+ init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "<leader>w", group = "Vimwiki" },
+ { "<leader>w<leader>", group = "Diary" },
+ })
+ end,
+ config = function()
+ -- Ensure files are read with the desired filetype
+ vim.g.vimwiki_ext2syntax = {
+ [".Rmd"] = "markdown",
+ [".rmd"] = "markdown",
+ [".md"] = "markdown",
+ [".markdown"] = "markdown",
+ [".mdown"] = "markdown",
+ }
+ -- Set up Vimwiki list
+ vim.g.vimwiki_list = {
+ {
+ path = vim.fn.expand("~/.local/share/vimwiki"),
+ syntax = "markdown",
+ ext = ".md",
+ },
+ }
+ end,
+ keys = {
+ { "<leader>ww", ":VimwikiIndex<CR>", desc = "Vimwiki index" },
+ },
+ },
+ {
+ "tools-life/taskwiki",
+ cmd = { "TaskWikiInfo", "TaskWikiSummary", "TaskWikiStart", "TaskWikiMod" },
+ dependencies = {
+ "vimwiki/vimwiki",
+ "powerman/vim-plugin-AnsiEsc",
+ "majutsushi/tagbar",
+ "farseer90718/vim-taskwarrior",
+ },
+ config = function()
+ require("taskwiki").setup()
+ vim.keymap.set("n", "<leader>tvi", ":TaskWikiInfo<CR>", { desc = "Task wiki info" })
+ vim.keymap.set("n", "<leader>tvS", ":TaskWikiSummary<CR>", { desc = "Task wiki summary" })
+ vim.keymap.set("n", "<leader>tvm", ":TaskWikiMod<CR>", { desc = "Task wiki modify" })
+ vim.keymap.set("n", "<leader>tvs", ":TaskWikiMod<CR>", { desc = "Task wiki modify" })
+ end,
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/zenmode.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/zenmode.lua
new file mode 100644
index 0000000..beb48f5
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/plugins/zenmode.lua
@@ -0,0 +1,46 @@
+return {
+ "folke/zen-mode.nvim",
+ opts = {},
+ config = function()
+ vim.keymap.set("n", "<leader>zz", function()
+ require("zen-mode").toggle({
+ -- callback where you can add custom code when the Zen window opens
+ on_open = function()
+ vim.wo.wrap = true
+ vim.wo.number = true
+ vim.wo.rnu = true
+ end,
+ -- callback where you can add custom code when the Zen window closes
+ on_close = function()
+ vim.wo.wrap = false
+ vim.wo.number = true
+ vim.wo.rnu = true
+ ColorMyPencils()
+ end,
+ })
+ end, { desc = "Toggle zenmode" })
+
+ vim.keymap.set("n", "<leader>zZ", function()
+ require("zen-mode").toggle({
+ window = {
+ width = 90,
+ },
+ -- callback where you can add custom code when the Zen window opens
+ on_open = function()
+ vim.wo.wrap = true
+ vim.wo.number = false
+ vim.wo.rnu = false
+ vim.opt.colorcolumn = "0"
+ ColorMyPencils("seoul256")
+ end,
+ -- callback where you can add custom code when the Zen window closes
+ on_close = function()
+ vim.wo.wrap = false
+ vim.wo.number = true
+ vim.wo.rnu = true
+ ColorMyPencils()
+ end,
+ })
+ end, { desc = "Toggle zenmode (custom)" })
+ end,
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/markdown.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/markdown.lua
new file mode 100644
index 0000000..7d91fab
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/markdown.lua
@@ -0,0 +1,187 @@
+local ls = require("luasnip")
+
+local s = ls.snippet
+local t = ls.text_node
+local i = ls.insert_node
+local f = ls.function_node
+
+local function clipboard()
+ return vim.fn.getreg("+")
+end
+
+-- #####################################################################
+-- Markdown
+-- #####################################################################
+
+-- Helper function to create code block snippets
+local function create_code_block_snippet(lang)
+ return s(lang, {
+ t({ "```" .. lang, "" }),
+ i(1),
+ t({ "", "```" }),
+ })
+end
+
+-- Define languages for code blocks
+local languages = {
+ "txt",
+ "lua",
+ "sql",
+ "go",
+ "regex",
+ "bash",
+ "markdown",
+ "markdown_inline",
+ "yaml",
+ "json",
+ "jsonc",
+ "cpp",
+ "csv",
+ "java",
+ "javascript",
+ "python",
+ "dockerfile",
+ "html",
+ "css",
+ "templ",
+ "php",
+}
+
+-- Generate snippets for all languages
+local snippets = {}
+
+for _, lang in ipairs(languages) do
+ table.insert(snippets, create_code_block_snippet(lang))
+end
+
+table.insert(
+ snippets,
+ s({
+ trig = "chirpy",
+ name = "Disable markdownlint and prettier for chirpy",
+ }, {
+ t({
+ " ",
+ "<!-- markdownlint-disable -->",
+ "<!-- prettier-ignore-start -->",
+ " ",
+ "<!-- tip=green, info=blue, warning=yellow, danger=red -->",
+ " ",
+ "> ",
+ }),
+ i(1),
+ t({
+ "",
+ "{: .prompt-",
+ }),
+ -- In case you want to add a default value "tip" here, but I'm having
+ -- issues with autosave
+ -- i(2, "tip"),
+ i(2),
+ t({
+ " }",
+ " ",
+ "<!-- prettier-ignore-end -->",
+ "<!-- markdownlint-restore -->",
+ }),
+ })
+)
+
+table.insert(
+ snippets,
+ s({
+ trig = "markdownlint",
+ name = "Add markdownlint disable and restore headings",
+ }, {
+ t({
+ " ",
+ "<!-- markdownlint-disable -->",
+ " ",
+ "> ",
+ }),
+ i(1),
+ t({
+ " ",
+ " ",
+ "<!-- markdownlint-restore -->",
+ }),
+ })
+)
+
+table.insert(
+ snippets,
+ s({
+ trig = "prettierignore",
+ name = "Add prettier ignore start and end headings",
+ }, {
+ t({
+ " ",
+ "<!-- prettier-ignore-start -->",
+ " ",
+ "> ",
+ }),
+ i(1),
+ t({
+ " ",
+ " ",
+ "<!-- prettier-ignore-end -->",
+ }),
+ })
+)
+
+table.insert(
+ snippets,
+ s({
+ trig = "link",
+ name = "Add this -> []()",
+ }, {
+ t("["),
+ i(1),
+ t("]("),
+ i(2),
+ t(")"),
+ })
+)
+
+table.insert(
+ snippets,
+ s({
+ trig = "linkt",
+ name = 'Add this -> [](){:target="_blank"}',
+ }, {
+ t("["),
+ i(1),
+ t("]("),
+ i(2),
+ t('){:target="_blank"}'),
+ })
+)
+
+table.insert(
+ snippets,
+ s({
+ trig = "todo",
+ name = "Add TODO: item",
+ }, {
+ t("<!-- TODO: "),
+ i(1),
+ t(" -->"),
+ })
+)
+
+-- Paste clipboard contents in link section, move cursor to ()
+table.insert(
+ snippets,
+ s({
+ trig = "linkclip",
+ name = "Paste clipboard as .md link",
+ }, {
+ t("["),
+ i(1),
+ t("]("),
+ f(clipboard, {}),
+ t(")"),
+ })
+)
+
+ls.add_snippets("markdown", snippets)
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode1.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode1.lua
new file mode 100644
index 0000000..85da742
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode1.lua
@@ -0,0 +1,103 @@
+local ls = require("luasnip")
+local s = ls.snippet
+local i = ls.insert_node
+local fmt = require("luasnip.extras.fmt").fmt
+local rep = require("luasnip.extras").rep
+local f = ls.function_node -- Import function_node for dynamic content
+
+-- Function to get the filename without the path
+local function get_filename()
+ local filepath = vim.api.nvim_buf_get_name(0)
+ local filename = vim.fn.fnamemodify(filepath, ":t:r") -- Get filename without path and extension
+ return filename:gsub("_", " ") -- Replace underscores with spaces
+end
+
+local neetcode_snippet = s(
+ "neetcode",
+ fmt(
+ [[
+"""
+Question
+
+{1}
+"""
+
+
+from typing import Dict
+
+class Solution:
+ """
+ A class to solve {file_name} from {2}
+ """
+ def {3}(self, {4}) -> {5}:
+ """
+ {file_name} from the input {parameters_type} using a {func_name} approach.
+
+ Args:
+ {parameters_type}: {6}
+
+ Returns:
+ {return_type}: {7}
+ """
+ return {8}
+
+ def print(self, examples: Dict[str, {parameters_type}]) -> None:
+ for name, example in examples.items():
+ result = self.{3}(example)
+ print(f"{{name}}: {{result}}")
+
+ def main(self):
+ cases = {{
+ "case1" : {9},
+ "case2" : {10},
+ "case3" : {11},
+ }}
+
+ self.print(cases)
+
+
+solution = Solution()
+solution.main()
+
+"""
+Solution
+
+url: {12}
+video: {13}
+
+1. {func_name}
+time: {14}
+space: {15}
+code:
+```python
+{16}
+```
+"""
+]],
+ {
+ i(1, '"Describe the question here"'),
+ file_name = f(get_filename), -- Insert the filename dynamicall
+ i(2, '"Describe the class here"'),
+ i(3, '"Function name"'), -- Primary insert node for method name
+ func_name = rep(3), -- Repeat the method name
+ i(4, '"parameters"'),
+ parameters_type = rep(4),
+ i(5, '"return_type"'),
+ return_type = rep(5),
+ i(6, '"parameters_desc"'),
+ i(7, '"return_type_desc"'),
+ i(8, '"return"'),
+ i(9, '"case1"'),
+ i(10, '"case2"'),
+ i(11, '"case3"'),
+ i(12, '"url"'),
+ i(13, '"video"'),
+ i(14, '"time_complexity"'),
+ i(15, '"space_complexity"'),
+ i(16, '"code"'),
+ }
+ )
+)
+
+-- Add the snippets for multiple filetypes
+ls.add_snippets("python", { neetcode_snippet })
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode2.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode2.lua
new file mode 100644
index 0000000..98e7258
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/neetcode2.lua
@@ -0,0 +1,104 @@
+local ls = require("luasnip")
+local s = ls.snippet
+local i = ls.insert_node
+local fmt = require("luasnip.extras.fmt").fmt
+local rep = require("luasnip.extras").rep
+local f = ls.function_node -- Import function_node for dynamic content
+
+-- Function to get the filename without the path
+local function get_filename()
+ local filepath = vim.api.nvim_buf_get_name(0)
+ local filename = vim.fn.fnamemodify(filepath, ":t:r") -- Get filename without path and extension
+ return filename:gsub("_", " ") -- Replace underscores with spaces
+end
+
+local neetcode_snippet = s(
+ "neetcode",
+ fmt(
+ [[
+"""
+Question
+
+{1}
+"""
+
+
+from typing import Dict
+
+class Solution:
+ """
+ A class to solve {file_name} from {2}
+ """
+ def {3}(self, {4}) -> {5}:
+ """
+ {file_name} from the input {parameters_type} using a {func_name} approach.
+
+ Args:
+ {parameters_type}: {6}
+
+ Returns:
+ {return_type}: {7}
+ """
+ return {8}
+
+ def print(self, examples: Dict[str, {parameters_type}]) -> None:
+ for name, (param1, param2) in examples.items():
+ result = self.{3}(param1, param2)
+ print(f"{{name}}: {{result}}")
+
+ def main(self):
+ cases = {{
+ "case1" : ({9}),
+ "case2" : ({10}),
+ "case3" : ({11}),
+ }}
+
+ self.print(cases)
+
+
+
+solution = Solution()
+solution.main()
+
+"""
+Solution
+
+url: {12}
+video: {13}
+
+1. {func_name}
+time: {14}
+space: {15}
+code:
+```python
+{16}
+```
+"""
+]],
+ {
+ i(1, '"Describe the question here"'),
+ file_name = f(get_filename), -- Insert the filename dynamicall
+ i(2, '"Describe the class here"'),
+ i(3, '"Function name"'), -- Primary insert node for method name
+ func_name = rep(3), -- Repeat the method name
+ i(4, '"parameters"'),
+ parameters_type = rep(4),
+ i(5, '"return_type"'),
+ return_type = rep(5),
+ i(6, '"parameters_desc"'),
+ i(7, '"return_type_desc"'),
+ i(8, '"return"'),
+ i(9, '"case1"'),
+ i(10, '"case2"'),
+ i(11, '"case3"'),
+ i(12, '"url"'),
+ i(13, '"video"'),
+ i(14, '"time_complexity"'),
+ i(15, '"space_complexity"'),
+ i(16, '"code"'),
+ }
+ )
+)
+
+-- Add the snippets for multiple filetypes
+ls.add_snippets("python", { neetcode_snippet })
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/quarto.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/quarto.lua
new file mode 100644
index 0000000..74b9fb8
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/quarto.lua
@@ -0,0 +1,28 @@
+local ls = require("luasnip")
+
+local s = ls.snippet
+local i = ls.insert_node
+local t = ls.text_node
+-- local d = ls.dynamic_node
+local c = ls.choice_node
+-- local f = ls.function_node
+-- local r = ls.restore_node
+local fmt = require("luasnip.extras.fmt").fmta
+-- local h = require("thesiahxyz.utils.snippet")
+
+local code_cell_snippet = s(
+ "`",
+ fmt(
+ [[```<lang>
+<last>
+``]],
+ {
+ lang = c(1, { t("python"), t("") }),
+ last = i(0), -- Place cursor here after expanding the snippet
+ }
+ )
+)
+
+-- Add the snippets for multiple filetypes
+ls.add_snippets("markdown", { code_cell_snippet })
+ls.add_snippets("quarto", { code_cell_snippet })
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/whichkey.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/whichkey.lua
new file mode 100644
index 0000000..fab5853
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/snippets/whichkey.lua
@@ -0,0 +1,18 @@
+local ls = require("luasnip")
+
+-- Using parse_snippet instead of fmt to avoid curly brace issues
+local whichkey_snippet = ls.parser.parse_snippet(
+ "whichkey",
+ [[
+init = function()
+ local wk = require("which-key")
+ wk.add({
+ mode = { "n" },
+ { "${1:Key}", group = "${2:Name}" },
+ })
+end,
+]]
+)
+
+-- Add the snippets for the Lua filetype
+ls.add_snippets("lua", { whichkey_snippet })
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add b/ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add
new file mode 100644
index 0000000..a60a241
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add
@@ -0,0 +1,113 @@
+#eplacement
+AI
+API
+ASAP
+AUR
+AWS
+ArchLinux
+Asana
+Azure
+BTW
+Bitbucket
+CD
+CI
+CLI
+CSS
+CV
+Cloudflare
+DNS
+DevOps
+DigitalOcean
+Django
+Docker
+ETA
+FAQ
+FYI
+FYI
+Fedora
+Figma
+Flask
+GCP
+GPG
+GUI
+Git
+GitHub
+GitLab
+Google
+HTML
+HTTP
+HTTPS
+IDE
+IDK
+IMO
+IP
+IoT
+JSON
+JWT
+JavaScript
+JetBrains
+Kubernetes
+Linode
+Linux
+Lorem
+Lua
+ML
+MVP
+Markdown
+MySQL
+NLP
+Neovim
+Node.js
+Notion
+OAuth
+OK
+OKRs
+POSIX
+PostgreSQL
+PyCharm
+PyPI
+R&D
+React
+Redis
+SQL
+SQLite
+SSH
+SSL
+Slack
+TBD
+TCP
+TLS
+Trello
+TypeScript
+UI
+URL
+UX
+Ubuntu
+VSCode
+Vue
+WebStorm
+YAML
+YouTube
+Zoom
+alt
+backend
+blockquote
+conda
+config
+ctrl
+dotfiles
+frontend
+highlighter
+inline
+ipsum
+kbd
+meta
+msmtp
+npm
+pip
+plaintext
+super
+syntax
+typst
+vimrc
+window
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add.spl b/ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add.spl
new file mode 100644
index 0000000..ab8358b
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/spells/en.utf-8.add.spl
Binary files differ
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet.lua
new file mode 100644
index 0000000..dd5130a
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet.lua
@@ -0,0 +1,13 @@
+local options = {
+ cheatsheet = {
+ theme = "grid", -- Options: "simple" or "grid"
+ excluded_groups = { "terminal (t)", "autopairs", "Nvim", "Opens" }, -- Exclude specific groups
+ },
+}
+
+-- Define a keymap for opening the cheatsheet
+vim.keymap.set("n", "<leader>skc", function()
+ require("thesiahxyz.utils.cheatsheet.grid")()
+end, { desc = "Open Cheatsheet" })
+
+return options
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/grid.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/grid.lua
new file mode 100644
index 0000000..6616b1e
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/grid.lua
@@ -0,0 +1,105 @@
+local api = vim.api
+local ch = require("thesiahxyz.utils.cheatsheet.init")
+local state = ch.state
+
+local ascii = {
+ " ",
+ " ",
+ "█▀▀ █░█ █▀▀ ▄▀█ ▀█▀ █▀ █░█ █▀▀ █▀▀ ▀█▀",
+ "█▄▄ █▀█ ██▄ █▀█ ░█░ ▄█ █▀█ ██▄ ██▄ ░█░",
+ " ",
+ " ",
+}
+
+return function(buf, win, action)
+ action = action or "open"
+
+ local ns = api.nvim_create_namespace("thesiah_cheatsheet")
+
+ if action == "open" then
+ state.mappings_tb = ch.organize_mappings()
+
+ buf = buf or api.nvim_create_buf(false, true)
+ win = win or api.nvim_get_current_win()
+
+ api.nvim_set_current_win(win)
+
+ -- Calculate maximum widths for lhs and rhs
+ local lhs_max_width, rhs_max_width = 0, 0
+ for _, section in pairs(state.mappings_tb) do
+ for _, mapping in ipairs(section) do
+ lhs_max_width = math.max(lhs_max_width, vim.fn.strdisplaywidth(mapping[1]))
+ rhs_max_width = math.max(rhs_max_width, vim.fn.strdisplaywidth(mapping[2]))
+ end
+ end
+
+ local total_width = lhs_max_width + rhs_max_width + 6 -- Add spacing for readability
+ local center_offset = math.floor((total_width - vim.fn.strdisplaywidth(ascii[1])) / 2)
+
+ -- Align ASCII art to the center
+ local ascii_header = vim.tbl_values(ascii)
+ for i, line in ipairs(ascii_header) do
+ ascii_header[i] = string.rep(" ", center_offset) .. line
+ end
+
+ -- Center-align the title
+ local title = "Cheatsheet"
+ local title_padding = math.floor((total_width - vim.fn.strdisplaywidth(title)) / 2)
+ local title_line = string.rep(" ", title_padding) .. title
+
+ -- Prepare buffer lines
+ local lines = {}
+ for _, line in ipairs(ascii_header) do
+ table.insert(lines, line)
+ end
+ table.insert(lines, "") -- Blank line after ASCII art
+ table.insert(lines, title_line)
+ table.insert(lines, "") -- Blank line after title
+
+ -- Add mappings grouped by section
+ for section_name, mappings in pairs(state.mappings_tb) do
+ -- Center-align the section name
+ local section_padding = math.floor((total_width - vim.fn.strdisplaywidth(section_name)) / 2)
+ table.insert(lines, string.rep(" ", section_padding) .. section_name) -- Section header
+
+ -- Add mappings aligned to lhs and rhs
+ for _, mapping in ipairs(mappings) do
+ local lhs = mapping[1]
+ local rhs = mapping[2]
+ local lhs_padding = string.rep(" ", lhs_max_width - vim.fn.strdisplaywidth(lhs))
+ local rhs_padding = string.rep(" ", rhs_max_width - vim.fn.strdisplaywidth(rhs))
+ table.insert(lines, lhs .. lhs_padding .. " " .. rhs_padding .. rhs)
+ end
+ table.insert(lines, "") -- Blank line after each section
+ end
+
+ -- Set lines into the buffer
+ api.nvim_buf_set_lines(buf, 0, -1, false, lines)
+
+ -- Highlight ASCII art and title
+ for i = 1, #ascii_header do
+ api.nvim_buf_add_highlight(buf, ns, "ThesiahAsciiHeader", i - 1, 0, -1)
+ end
+ api.nvim_buf_add_highlight(buf, ns, "Title", #ascii + 1, 0, -1)
+
+ -- Highlight section headers
+ local current_line = #ascii + 3 -- Adjust for blank lines and title
+ for section_name, mappings in pairs(state.mappings_tb) do
+ api.nvim_buf_add_highlight(buf, ns, "ThesiahSection", current_line, 0, -1)
+ current_line = current_line + #mappings + 2 -- Count section header, mappings, and blank line
+ end
+
+ -- Configure the buffer
+ vim.bo[buf].modifiable = false
+ vim.bo[buf].readonly = false
+ vim.bo[buf].buftype = ""
+ vim.bo[buf].buflisted = true
+ vim.bo[buf].filetype = "cheatsheet"
+
+ -- Set up autocmds for the cheatsheet
+ ch.autocmds(buf, win)
+
+ -- Focus on the cheatsheet buffer
+ api.nvim_set_current_buf(buf)
+ end
+end
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/init.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/init.lua
new file mode 100644
index 0000000..c4cc1f0
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/cheatsheet/init.lua
@@ -0,0 +1,116 @@
+local M = {}
+local api = vim.api
+local config = require("thesiahxyz.utils.cheatsheet") -- Load cheatsheet options
+
+local function capitalize(str)
+ return (str:gsub("^%l", string.upper))
+end
+
+M.get_mappings = function(mappings, tb_to_add)
+ local excluded_groups = config.cheatsheet.excluded_groups
+
+ for _, v in ipairs(mappings) do
+ local desc = v.desc
+
+ if not desc or (select(2, desc:gsub("%S+", "")) <= 1) or string.find(desc, "\n") then
+ goto continue
+ end
+
+ local heading = desc:match("%S+") -- Get first word
+ heading = (v.mode ~= "n" and heading .. " (" .. v.mode .. ")") or heading
+
+ if
+ vim.tbl_contains(excluded_groups, heading)
+ or vim.tbl_contains(excluded_groups, desc:match("%S+"))
+ or string.find(v.lhs, "<Plug>")
+ then
+ goto continue
+ end
+
+ heading = capitalize(heading)
+
+ if not tb_to_add[heading] then
+ tb_to_add[heading] = {}
+ end
+
+ local keybind = string.sub(v.lhs, 1, 1) == " " and "<leader> +" .. v.lhs or v.lhs
+
+ desc = v.desc:match("%s(.+)") -- Remove first word from description
+ desc = capitalize(desc)
+
+ table.insert(tb_to_add[heading], { desc, keybind })
+
+ ::continue::
+ end
+end
+
+M.organize_mappings = function()
+ local tb_to_add = {}
+ local modes = { "n", "i", "v", "t" }
+
+ for _, mode in ipairs(modes) do
+ local keymaps = api.nvim_get_keymap(mode)
+ M.get_mappings(keymaps, tb_to_add)
+
+ local bufkeymaps = api.nvim_buf_get_keymap(0, mode)
+ M.get_mappings(bufkeymaps, tb_to_add)
+ end
+
+ return tb_to_add
+end
+
+M.rand_hlgroup = function()
+ local hlgroups = {
+ "blue",
+ "red",
+ "green",
+ "yellow",
+ "orange",
+ "baby_pink",
+ "purple",
+ "white",
+ "cyan",
+ "vibrant_green",
+ "teal",
+ }
+
+ return "ThesiahHead" .. hlgroups[math.random(1, #hlgroups)]
+end
+
+M.autocmds = function(buf, win)
+ -- Set buffer options to make it searchable and navigable
+ vim.bo[buf].buflisted = true
+ vim.bo[buf].buftype = "" -- Treat it as a regular buffer
+ vim.bo[buf].swapfile = false
+ vim.bo[buf].modifiable = false -- Prevent accidental edits
+ vim.bo[buf].readonly = false -- Enable navigation and search
+ vim.bo[buf].filetype = "cheatsheet" -- Optional, to customize behavior
+
+ -- Create autocmd group for cheatsheet
+ local group_id = api.nvim_create_augroup("ThesiahCheatsheet", { clear = true })
+
+ -- Clean up when buffer is closed
+ api.nvim_create_autocmd("BufWinLeave", {
+ group = group_id,
+ buffer = buf,
+ callback = function()
+ vim.g.thesiah_cheatsheet_displayed = false
+ api.nvim_del_augroup_by_id(group_id)
+ end,
+ })
+
+ -- Keymaps for cheatsheet buffer
+ vim.keymap.set("n", "q", function()
+ api.nvim_buf_delete(buf, { force = true })
+ end, { buffer = buf })
+
+ vim.keymap.set("n", "<ESC>", function()
+ api.nvim_buf_delete(buf, { force = true })
+ end, { buffer = buf })
+end
+
+M.state = {
+ mappings_tb = {},
+}
+
+return M
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/icons.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/icons.lua
new file mode 100644
index 0000000..22d5f2d
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/icons.lua
@@ -0,0 +1,168 @@
+return {
+ diagnostics = {
+ Error = " ",
+ Hint = "󰠠 ",
+ Information = " ",
+ Question = " ",
+ Warning = " ",
+ },
+ documents = {
+ File = " ",
+ FileEmpty = " ",
+ Files = " ",
+ Folder = " ",
+ FolderEmpty = " ",
+ OpenFolder = " ",
+ OpenFolderEmpty = " ",
+ SymLink = " ",
+ SymlinkFolder = " ",
+ Import = " ",
+ },
+ git = {
+ Add = " ",
+ AddAlt = " ",
+ Branch = " ",
+ Diff = " ",
+ DiffAlt = " ",
+ Ignore = "◌ ",
+ Mod = " ",
+ Octoface = " ",
+ Remove = " ",
+ RemoveAlt = " ",
+ Rename = " ",
+ Repo = " ",
+ Tag = " ",
+ Untrack = " ",
+ },
+ kind = {
+ Class = " ",
+ Color = " ",
+ Constant = " ",
+ Constructor = "󰈏 ",
+ Copilot = " ",
+ Enum = " ",
+ EnumMember = " ",
+ Event = " ",
+ Field = " ",
+ File = " ",
+ Folder = " ",
+ Function = "󰊕 ",
+ Interface = " ",
+ Keyword = " ",
+ Language = "󱀍 ",
+ Method = " ",
+ Module = "",
+ Operator = " ",
+ Property = " ",
+ Reference = " ",
+ Snippet = " ",
+ Struct = " ",
+ Text = " ",
+ TypeParameter = " ",
+ Unit = " ",
+ Value = " ",
+ Variable = " ",
+ },
+ type = {
+ Array = " ",
+ Boolean = "⏻ ",
+ Number = " ",
+ Object = " ",
+ String = " ",
+ },
+ ui = {
+ Arrow = " ",
+ ArrowClosed = " ",
+ ArrowLeft = " ",
+ ArrowOpen = " ",
+ ArrowRight = " ",
+ Bluetooth = " ",
+ Bookmark = " ",
+ Bug = " ",
+ Calendar = " ",
+ Camera = " ",
+ Check = " ",
+ ChevronRight = "",
+ Circle = " ",
+ CircleSmall = "● ",
+ CircleSmallEmpty = "○ ",
+ Clipboard = " ",
+ Close = " ",
+ Code = " ",
+ Collection = " ",
+ Color = " ",
+ Command = " ",
+ Comment = " ",
+ Copilot = " ",
+ CopilotError = " ",
+ Corner = "└ ",
+ Dashboard = " ",
+ Database = " ",
+ Download = " ",
+ Edit = " ",
+ Edge = "│ ",
+ Electric = " ",
+ Eye = " ",
+ Fire = " ",
+ Firefox = " ",
+ Flag = " ",
+ Game = " ",
+ Gear = " ",
+ GitHub = " ",
+ Heart = " ",
+ History = " ",
+ Home = " ",
+ Incoming = " ",
+ Jump = " ",
+ Keyboard = " ",
+ Ligthbulb = "󰌵 ",
+ List = "",
+ Lock = " ",
+ Minus = "‒ ",
+ Music = "󰝚 ",
+ Neovim = " ",
+ NewFile = " ",
+ None = " ",
+ Note = " ",
+ Outgoing = " ",
+ Package = " ",
+ Paint = " ",
+ Pause = " ",
+ Pencil = " ",
+ Person = " ",
+ Pin = " ",
+ Play = " ",
+ Plug = " ",
+ Plus = " ",
+ Power = " ",
+ PowerlineArrowLeft = "",
+ PowerlineArrowRight = "",
+ PowerlineLeftRound = "",
+ PowerlineRightRound = "",
+ Project = " ",
+ Question = " ",
+ Refresh = " ",
+ Reload = " ",
+ Rocket = " ",
+ Save = "󰆓 ",
+ Search = " ",
+ Separator = "▊ ",
+ SeparatorLight = "▍",
+ SeparatorDashed = "┆",
+ SignIn = " ",
+ SignOut = " ",
+ Sleep = "󰒲 ",
+ Star = " ",
+ Table = " ",
+ Telescope = " ",
+ Terminal = " ",
+ Test = " ",
+ Time = " ",
+ Topline = "‾",
+ Trash = " ",
+ User = " ",
+ Vim = " ",
+ Wifi = " ",
+ Windows = " ",
+ },
+}
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/markdown.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/markdown.lua
new file mode 100644
index 0000000..1b1c591
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/markdown.lua
@@ -0,0 +1,26 @@
+local M = {}
+
+-- foldtext for Neovim < 0.10.0
+function M.foldtext()
+ return vim.api.nvim_buf_get_lines(0, vim.v.lnum - 1, vim.v.lnum, false)[1]
+end
+
+-- optimized treesitter foldexpr for Neovim >= 0.10.0
+function M.foldexpr()
+ local buf = vim.api.nvim_get_current_buf()
+ if vim.b[buf].ts_folds == nil then
+ -- as long as we don't have a filetype, don't bother
+ -- checking if treesitter is available (it won't)
+ if vim.bo[buf].filetype == "" then
+ return "0"
+ end
+ if vim.bo[buf].filetype:find("dashboard") then
+ vim.b[buf].ts_folds = false
+ else
+ vim.b[buf].ts_folds = pcall(vim.treesitter.get_parser, buf)
+ end
+ end
+ return vim.b[buf].ts_folds and vim.treesitter.foldexpr() or "0"
+end
+
+return M
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/snippet.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/snippet.lua
new file mode 100644
index 0000000..57bb211
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/snippet.lua
@@ -0,0 +1,38 @@
+local ls = require("luasnip")
+-- local s = ls.snippet
+local i = ls.insert_node
+local t = ls.text_node
+-- local d = ls.dynamic_node
+-- local f = ls.function_node
+local c = ls.choice_node
+-- local rep = require("luasnip.extras").rep
+-- local k = require("luasnip.nodes.key_indexer").new_key
+local r = ls.restore_node
+local fmt = require("luasnip.extras.fmt").fmt
+
+local M = {}
+M.i = 0
+
+M.js_quotes = function(index)
+ M.i = M.i + 1
+ return c(index, { fmt("'{}'", { r(1, M.i) }), fmt("`{}`", { r(1, M.i) }), fmt('"{}"', { r(1, M.i) }), t("") })
+end
+
+M.js_selector = function(index)
+ return c(index, { fmt("/{}/i", { i(1) }), fmt("'{}'", { i(1) }), fmt("`{}`", { i(1) }), t("") })
+end
+
+M.rtl_selector = function(index)
+ return c(index, { t("screen.get"), t("await screen.find"), t("screen.query") })
+end
+
+M.tracked_i_nodes = {}
+
+M.tin = function(index, key)
+ if not M.tracked_i_nodes[key] then
+ M.tracked_i_nodes[key] = i(index)
+ end
+ return M.tracked_i_nodes[key]
+end
+
+return M
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tasks.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tasks.lua
new file mode 100644
index 0000000..34d5636
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tasks.lua
@@ -0,0 +1,227 @@
+-- ~/.config/nvim/lua/user_functions/tasks.lua
+local M = {}
+
+-- Import the create_floating_scratch function from utils.lua
+local utils = require("thesiahxyz.utils.utils")
+
+function M.create_or_update_task()
+ local current_line = vim.fn.getline(".")
+ local cursor_pos = vim.fn.col(".")
+ local file_path = vim.fn.expand("%:p") -- Get full path of current file
+ local line_number = vim.fn.line(".") -- Get current line number
+
+ -- Keywords we are looking for
+ local keywords = { "TODO", "HACK", "NOTE", "PERF", "TEST", "WARN" }
+
+ for _, keyword in ipairs(keywords) do
+ local start_index, end_index = string.find(current_line, keyword)
+ if start_index then
+ local task_description = string.sub(current_line, end_index + 2, cursor_pos - 1)
+ task_description = string.gsub(task_description, "%(siah%)", "")
+ local task_tag = "+" .. string.lower(keyword)
+
+ -- Ask for project and other tags
+ local project = vim.fn.input("Enter project name: ")
+ local additional_tags_input = vim.fn.input("Enter additional tags separated by spaces: ")
+ local additional_tags = {}
+
+ -- Prefix each additional tag with a "+"
+ for tag in additional_tags_input:gmatch("%S+") do
+ table.insert(additional_tags, "+" .. tag)
+ end
+
+ -- Prepare the task command
+ local task_cmd = string.format('task add %s "%s"', task_tag, task_description)
+
+ -- Add additional tags if available
+ if #additional_tags > 0 then
+ task_cmd = task_cmd .. " " .. table.concat(additional_tags, " ")
+ end
+
+ -- Add project if available
+ if project and #project > 0 then
+ task_cmd = task_cmd .. " project:" .. project
+ end
+
+ -- Execute the task add command
+ local output = vim.fn.system(task_cmd)
+ print("Output: ", output)
+
+ for line in output:gmatch("[^\r\n]+") do
+ local task_id = string.match(line, "Created task (%d+)%.")
+ if task_id then
+ print("Task ID extracted: ", task_id)
+
+ -- Annotate task with filename and line number in the nvimline format
+ local annotation = string.format("nvimline:%s:%s", line_number, file_path)
+ local annotate_cmd = string.format('task %s annotate "%s"', task_id, annotation)
+ local annotate_output = vim.fn.system(annotate_cmd)
+
+ print("Annotation output: ", annotate_output)
+ return
+ else
+ print("Failed to extract task ID")
+ end
+ end
+ end
+ end
+end
+
+vim.api.nvim_set_keymap(
+ "i",
+ "<C-t>",
+ "<cmd>lua require('thesiahxyz.utils.tasks').create_or_update_task()<cr>",
+ { noremap = true, silent = true, desc = "Create/Update task" }
+)
+
+function M.mark_task_done()
+ -- Get the current line and parse it
+ local line = vim.api.nvim_get_current_line()
+ print("Original line: ", line)
+
+ -- Uncomment the line
+ vim.cmd("normal! gcc")
+ line = vim.api.nvim_get_current_line()
+ -- Remove (piotr1215) from the line
+ line = string.gsub(line, "%s*%(siah%)%s*", " ")
+ print("Uncommented line: ", line)
+
+ local patterns = { "TODO:", "HACK:", "NOTE:", "PERF:", "TEST:", "WARN:" }
+ local taskDescription = nil
+ for _, pattern in ipairs(patterns) do
+ local start_idx = string.find(line, pattern)
+ if start_idx then
+ taskDescription = string.sub(line, start_idx + string.len(pattern) + 1)
+ break
+ end
+ end
+ print("Task description: ", taskDescription or "nil")
+
+ -- If a task description was found, mark it as done
+ if taskDescription then
+ local output = vim.fn.system("yes | task description~'" .. taskDescription .. "' done")
+ print("Command output: ", output)
+ -- Check the command's output to make sure the task was marked done
+ if string.find(output, "Completed") then
+ -- Delete the current line
+ vim.cmd([[normal dd]])
+ end
+ end
+end
+
+vim.api.nvim_set_keymap(
+ "n",
+ "<localleader>td",
+ "<Cmd>lua require('thesiahxyz.utils.tasks').mark_task_done()<CR>",
+ { noremap = true, silent = true, desc = "Mark task done" }
+)
+
+function M.go_to_task_in_taskwarrior_tui()
+ -- Get the current line and save it as the original line
+ local original_line = vim.api.nvim_get_current_line()
+
+ -- Uncomment the line
+ vim.cmd("normal! gcc")
+ local uncommented_line = vim.api.nvim_get_current_line()
+
+ local patterns = { "TODO:", "HACK:", "NOTE:", "PERF:", "TEST:", "WARN:" }
+ local taskDescription = nil
+
+ for _, pattern in ipairs(patterns) do
+ local start_idx = string.find(uncommented_line, pattern)
+ if start_idx then
+ taskDescription = string.sub(uncommented_line, start_idx + string.len(pattern) + 1)
+ taskDescription = string.sub(taskDescription, 1, 50)
+ break
+ end
+ end
+
+ -- If a task description was found, use it to go to the task in taskwarrior-tui
+ if taskDescription then
+ -- print("Sleeping for 2 seconds before tmux switch...")
+ -- vim.cmd("sleep 2") -- sleep for 2 seconds
+ local output = vim.fn.system(" ~/.local/bin/opentasktui '" .. taskDescription .. "'")
+ end
+
+ -- Replace the line back with the original
+ vim.api.nvim_set_current_line(original_line)
+end
+
+vim.api.nvim_set_keymap(
+ "n",
+ "<localleader>tt",
+ "<Cmd>lua require('thesiahxyz.utils.tasks').go_to_task_in_taskwarrior_tui()<CR>",
+ { noremap = true, silent = true, desc = "Open taskwarrior-tui" }
+)
+
+function M.process_task_list(start_line, end_line, ...)
+ local args = { ... }
+ local modifiers = table.concat(args, " ")
+ local lines
+
+ -- If no range is provided, use the entire buffer.
+ if not start_line or not end_line then
+ start_line, end_line = 1, vim.api.nvim_buf_line_count(0)
+ end
+
+ lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
+
+ local new_lines = { "#!/bin/sh", "set -e" }
+
+ for _, line in ipairs(lines) do
+ local trimmed_line = line:gsub("^[•*%-%+]+%s*", "")
+ local links = {}
+
+ trimmed_line = trimmed_line:gsub("(https?://[%w%.%-%_/&%?=%~]+)", function(link)
+ table.insert(links, link)
+ return ""
+ end)
+
+ if #trimmed_line > 0 then
+ -- No more "\n" before "# Adding task:"; instead, just ensure it's a new entry in the table.
+ table.insert(new_lines, "") -- Ensure there's an empty line before adding a new task if desired.
+ table.insert(new_lines, "# Adding task: " .. trimmed_line)
+ table.insert(
+ new_lines,
+ "output=$(task add " .. (modifiers ~= "" and modifiers .. " " or "") .. '"' .. trimmed_line .. '")'
+ )
+ table.insert(
+ new_lines,
+ 'task_id=$(echo "$output" | grep -o "Created task [0-9]*." | cut -d " " -f 3 | tr -d ".")'
+ )
+
+ for _, link in ipairs(links) do
+ table.insert(new_lines, "task $task_id annotate -- " .. link)
+ end
+ end
+ end
+
+ -- Call the create_floating_scratch function from utils.lua
+ utils.create_floating_scratch(new_lines)
+end
+
+function M.my_custom_complete(arg_lead, cmd_line, cursor_pos)
+ -- This is your list of arguments.
+ local items = { "project:", "due:", "+next", "duration:" }
+
+ -- Filter the items based on the argument lead.
+ local matches = {}
+ for _, item in ipairs(items) do
+ if item:find(arg_lead) == 1 then
+ table.insert(matches, item)
+ end
+ end
+
+ return matches
+end
+
+vim.api.nvim_create_user_command("ProcessTaskList", function(input)
+ M.process_task_list(1, vim.api.nvim_buf_line_count(0), unpack(input.fargs))
+end, {
+ nargs = "*",
+ complete = function(arg_lead, cmd_line, cursor_pos)
+ -- Call the custom completion function
+ return M.my_custom_complete(arg_lead, cmd_line, cursor_pos)
+ end,
+})
+return M
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tmux.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tmux.lua
new file mode 100644
index 0000000..41869f8
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/tmux.lua
@@ -0,0 +1,63 @@
+local M = {}
+
+M.tmux_pane_function = function(dir)
+ -- NOTE: variable that controls the auto-cd behavior
+ local auto_cd_to_new_dir = true
+ -- NOTE: Variable to control pane direction: 'right' or 'bottom'
+ -- If you modify this, make sure to also modify TMUX_PANE_DIRECTION in the
+ -- zsh-vi-mode section on the .zshrc file
+ -- Also modify this in your tmux.conf file if you want it to work when in tmux
+ -- copy-mode
+ local pane_direction = vim.g.tmux_pane_direction or "right"
+ -- NOTE: Below, the first number is the size of the pane if split horizontally,
+ -- the 2nd number is the size of the pane if split vertically
+ local pane_size = (pane_direction == "right") and 60 or 15
+ local move_key = (pane_direction == "right") and "C-l" or ""
+ local split_cmd = (pane_direction == "right") and "-h" or "-v"
+ -- if no dir is passed, use the current file's directory
+ local file_dir = dir or vim.fn.expand("%:p:h")
+ -- Simplified this, was checking if a pane existed
+ local has_panes = vim.fn.system("tmux list-panes | wc -l"):gsub("%s+", "") ~= "1"
+ -- Check if the current pane is zoomed (maximized)
+ local is_zoomed = vim.fn.system("tmux display-message -p '#{window_zoomed_flag}'"):gsub("%s+", "") == "1"
+ -- Escape the directory path for shell
+ local escaped_dir = file_dir:gsub("'", "'\\''")
+ -- If any additional pane exists
+ if has_panes then
+ if is_zoomed then
+ -- Compare the stored pane directory with the current file directory
+ if auto_cd_to_new_dir and vim.g.tmux_pane_dir ~= escaped_dir then
+ -- If different, cd into the new dir
+ vim.fn.system("tmux send-keys -t :.+ 'cd \"" .. escaped_dir .. "\"' Enter")
+ -- Update the stored directory to the new one
+ vim.g.tmux_pane_dir = escaped_dir
+ end
+ -- If zoomed, unzoom and switch to the correct pane
+ vim.fn.system("tmux resize-pane -Z")
+ vim.fn.system("tmux send-keys " .. move_key)
+ else
+ -- If not zoomed, zoom current pane
+ vim.fn.system("tmux resize-pane -Z")
+ end
+ else
+ -- Store the initial directory in a Neovim variable
+ if vim.g.tmux_pane_dir == nil then
+ vim.g.tmux_pane_dir = escaped_dir
+ end
+ -- If no pane exists, open it with zsh and DISABLE_PULL variable
+ vim.fn.system(
+ "tmux split-window "
+ .. split_cmd
+ .. " -l "
+ .. pane_size
+ .. " 'cd \""
+ .. escaped_dir
+ .. "\" && DISABLE_PULL=1 zsh'"
+ )
+ vim.fn.system("tmux send-keys " .. move_key)
+ end
+end
+
+vim.keymap.set({ "n", "v", "i" }, "<M-\\>", M.tmux_pane_function, { desc = "Toggle terminal in tmux" })
+
+return M
diff --git a/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/utils.lua b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/utils.lua
new file mode 100644
index 0000000..2925569
--- /dev/null
+++ b/ar/.config/TheSiahxyz/lua/thesiahxyz/utils/utils.lua
@@ -0,0 +1,112 @@
+-- ~/.config/nvim/lua/thesiahxyz/utils/utils.lua
+local M = {}
+
+function M.print_current_file_dir()
+ local dir = vim.fn.expand("%:p:h")
+ if dir ~= "" then
+ print(dir)
+ end
+end
+
+function M.reload_module(name)
+ package.loaded[name] = nil
+ return require(name)
+end
+
+-- Function to reload the current Lua file
+function M.reload_current_file()
+ local current_file = vim.fn.expand("%:p")
+
+ if current_file:match("%.lua$") then
+ vim.cmd("luafile " .. current_file)
+ print("Reloaded file: " .. current_file)
+ else
+ print("Current file is not a Lua file: " .. current_file)
+ end
+end
+
+function M.insert_file_path()
+ local actions = require("telescope.actions")
+ local action_state = require("telescope.actions.state")
+
+ require("telescope.builtin").find_files({
+ cwd = "~/.config", -- Set the directory to search
+ attach_mappings = function(_, map)
+ map("i", "<CR>", function(prompt_bufnr)
+ local selected_file = action_state.get_selected_entry(prompt_bufnr).path
+ actions.close(prompt_bufnr)
+
+ -- Replace the home directory with ~
+ selected_file = selected_file:gsub(vim.fn.expand("$HOME"), "~")
+
+ -- Ask the user if they want to insert the full path or just the file name
+ local choice = vim.fn.input("Insert full path or file name? (n[ame]/p[ath]): ")
+ local text_to_insert
+ if choice == "p" then
+ text_to_insert = selected_file
+ elseif choice == "n" then
+ text_to_insert = vim.fn.fnamemodify(selected_file, ":t")
+ end
+
+ -- Move the cursor back one position
+ local col = vim.fn.col(".") - 1
+ vim.fn.cursor(vim.fn.line("."), col)
+
+ -- Insert the text at the cursor position
+ vim.api.nvim_put({ text_to_insert }, "c", true, true)
+ end)
+ return true
+ end,
+ })
+end
+
+vim.api.nvim_set_keymap(
+ "i",
+ "<M-p>",
+ "<cmd>lua require('thesiahxyz.utils.utils').insert_file_path()<cr>",
+ { noremap = true, silent = true }
+)
+function M.create_floating_scratch(content)
+ -- Get editor dimensions
+ local width = vim.api.nvim_get_option("columns")
+ local height = vim.api.nvim_get_option("lines")
+
+ -- Calculate the floating window size
+ local win_height = math.ceil(height * 0.8) + 2 -- Adding 2 for the border
+ local win_width = math.ceil(width * 0.8) + 2 -- Adding 2 for the border
+
+ -- Calculate window's starting position
+ local row = math.ceil((height - win_height) / 2)
+ local col = math.ceil((width - win_width) / 2)
+
+ -- Create a buffer and set it as a scratch buffer
+ local buf = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(buf, "buftype", "nofile")
+ vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe")
+ vim.api.nvim_buf_set_option(buf, "filetype", "sh") -- for syntax highlighting
+
+ -- Create the floating window with a border and set some options
+ local win = vim.api.nvim_open_win(buf, true, {
+ relative = "editor",
+ row = row,
+ col = col,
+ width = win_width,
+ height = win_height,
+ border = "single", -- You can also use 'double', 'rounded', or 'solid'
+ })
+
+ -- Check if we've got content to populate the buffer with
+ if content then
+ vim.api.nvim_buf_set_lines(buf, 0, -1, true, content)
+ else
+ vim.api.nvim_buf_set_lines(buf, 0, -1, true, { "This is a scratch buffer in a floating window." })
+ end
+
+ vim.api.nvim_win_set_option(win, "wrap", false)
+ vim.api.nvim_win_set_option(win, "cursorline", true)
+
+ -- Map 'q' to close the buffer in this window
+ vim.api.nvim_buf_set_keymap(buf, "n", "q", ":q!<CR>", { noremap = true, silent = true })
+end
+
+return M
diff --git a/ar/.config/asus_jack_audio_issue.txt b/ar/.config/asus_jack_audio_issue.txt
new file mode 100644
index 0000000..baf0984
--- /dev/null
+++ b/ar/.config/asus_jack_audio_issue.txt
@@ -0,0 +1,7 @@
+# static noise bacause of the power saving.
+# create this file.
+# /etc/modprobe.d/audio_powersave.conf
+
+# add these lines into the file.
+options snd_hda_intel power_save=0
+options snd_ac97_codec power_save=0
diff --git a/ar/.config/atuin/config.toml b/ar/.config/atuin/config.toml
new file mode 100644
index 0000000..07f6a18
--- /dev/null
+++ b/ar/.config/atuin/config.toml
@@ -0,0 +1,210 @@
+## where to store your database, default is your system data directory
+## linux/mac: ~/.local/share/atuin/history.db
+## windows: %USERPROFILE%/.local/share/atuin/history.db
+db_path = "~/.local/share/history/atuin-history.db"
+
+## where to store your encryption key, default is your system data directory
+## linux/mac: ~/.local/share/atuin/key
+## windows: %USERPROFILE%/.local/share/atuin/key
+key_path = "~/.local/share/history/atuin-key"
+
+## where to store your auth session token, default is your system data directory
+## linux/mac: ~/.local/share/atuin/session
+## windows: %USERPROFILE%/.local/share/atuin/session
+session_path = "~/.local/share/history/atuin-session"
+
+## date format used, either "us" or "uk"
+dialect = "us"
+
+## default timezone to use when displaying time
+## either "l", "local" to use the system's current local timezone, or an offset
+## from UTC in the format of "<+|->H[H][:M[M][:S[S]]]"
+## for example: "+9", "-05", "+03:30", "-01:23:45", etc.
+timezone = "local"
+
+## enable or disable automatic sync
+auto_sync = true
+
+## enable or disable automatic update checks
+update_check = true
+
+## address of the sync server
+sync_address = "https://api.atuin.sh"
+
+## how often to sync history. note that this is only triggered when a command
+## is ran, so sync intervals may well be longer
+## set it to 0 to sync after every command
+sync_frequency = "60m"
+
+## which search mode to use
+## possible values: prefix, fulltext, fuzzy, skim
+search_mode = "fuzzy"
+
+## which filter mode to use
+## possible values: global, host, session, directory
+filter_mode = "workspace"
+
+## With workspace filtering enabled, Atuin will filter for commands executed
+## in any directory within a git repository tree (default: false)
+##
+## To use workspace mode by default when available, set this to true and
+## set filter_mode to "workspace" or leave it unspecified and
+## set search.filters to include "workspace" before other filter modes.
+workspaces = true
+
+## which filter mode to use when atuin is invoked from a shell up-key binding
+## the accepted values are identical to those of "filter_mode"
+## leave unspecified to use same mode set in "filter_mode"
+filter_mode_shell_up_key_binding = "directory"
+
+## which search mode to use when atuin is invoked from a shell up-key binding
+## the accepted values are identical to those of "search_mode"
+## leave unspecified to use same mode set in "search_mode"
+search_mode_shell_up_key_binding = "fuzzy"
+
+## which style to use
+## possible values: auto, full, compact
+style = "auto"
+
+## the maximum number of lines the interface should take up
+## set it to 0 to always go full screen
+# inline_height = 0
+
+## Invert the UI - put the search bar at the top , Default to `false`
+invert = false
+
+## enable or disable showing a preview of the selected command
+## useful when the command is longer than the terminal width and is cut off
+show_preview = true
+
+## what to do when the escape key is pressed when searching
+## possible values: return-original, return-query
+# exit_mode = "return-original"
+
+## possible values: emacs, subl
+# word_jump_mode = "emacs"
+
+## characters that count as a part of a word
+# word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+## number of context lines to show when scrolling by pages
+# scroll_context_lines = 1
+
+## use ctrl instead of alt as the shortcut modifier key for numerical UI shortcuts
+## alt-0 .. alt-9
+ctrl_n_shortcuts = true
+
+## default history list format - can also be specified with the --format arg
+# history_format = "{time}\t{command}\t{duration}"
+
+## prevent commands matching any of these regexes from being written to history.
+## Note that these regular expressions are unanchored, i.e. if they don't start
+## with ^ or end with $, they'll match anywhere in the command.
+## For details on the supported regular expression syntax, see
+## https://docs.rs/regex/latest/regex/#syntax
+# history_filter = [
+# "^secret-cmd",
+# "^innocuous-cmd .*--secret=.+",
+# ]
+
+## prevent commands run with cwd matching any of these regexes from being written
+## to history. Note that these regular expressions are unanchored, i.e. if they don't
+## start with ^ or end with $, they'll match anywhere in CWD.
+## For details on the supported regular expression syntax, see
+## https://docs.rs/regex/latest/regex/#syntax
+# cwd_filter = [
+# "^/very/secret/area",
+# ]
+
+## Configure the maximum height of the preview to show.
+## Useful when you have long scripts in your history that you want to distinguish
+## by more than the first few lines.
+max_preview_height = 8
+
+## Configure whether or not to show the help row, which includes the current Atuin
+## version (and whether an update is available), a keymap hint, and the total
+## amount of commands in your history.
+# show_help = true
+
+## Configure whether or not to show tabs for search and inspect
+# show_tabs = true
+
+## Configure whether or not the tabs row may be auto-hidden, which includes the current Atuin
+## tab, such as Search or Inspector, and other tabs you may wish to see. This will
+## only be hidden if there are fewer than this count of lines available, and does not affect the use
+## of keyboard shortcuts to switch tab. 0 to never auto-hide, default is 8 (lines).
+## This is ignored except in `compact` mode.
+# auto_hide_height = 8
+
+## Defaults to true. This matches history against a set of default regex, and will not save it if we get a match. Defaults include
+## 1. AWS key id
+## 2. Github pat (old and new)
+## 3. Slack oauth tokens (bot, user)
+## 4. Slack webhooks
+## 5. Stripe live/test keys
+# secrets_filter = true
+
+## Defaults to true. If enabled, upon hitting enter Atuin will immediately execute the command. Press tab to return to the shell and edit.
+# This applies for new installs. Old installs will keep the old behaviour unless configured otherwise.
+enter_accept = true
+
+## Defaults to "emacs". This specifies the keymap on the startup of `atuin
+## search`. If this is set to "auto", the startup keymap mode in the Atuin
+## search is automatically selected based on the shell's keymap where the
+## keybinding is defined. If this is set to "emacs", "vim-insert", or
+## "vim-normal", the startup keymap mode in the Atuin search is forced to be
+## the specified one.
+keymap_mode = "auto"
+
+## Cursor style in each keymap mode. If specified, the cursor style is changed
+## in entering the cursor shape. Available values are "default" and
+## "{blink,steady}-{block,underline,bar}".
+keymap_cursor = { emacs = "blink-block", vim_insert = "blink-bar", vim_normal = "steady-block" }
+
+# network_connect_timeout = 5
+# network_timeout = 5
+
+## Timeout (in seconds) for acquiring a local database connection (sqlite)
+# local_timeout = 5
+
+## Set this to true and Atuin will minimize motion in the UI - timers will not update live, etc.
+## Alternatively, set env NO_MOTION=true
+# prefers_reduced_motion = false
+
+[stats]
+## Set commands where we should consider the subcommand for statistics. Eg, kubectl get vs just kubectl
+common_subcommands = [
+# "apt",
+# "cargo",
+# "composer",
+# "dnf",
+ "docker",
+ "git",
+# "go",
+ "ip",
+# "kubectl",
+# "nix",
+ "nmcli",
+# "npm",
+# "pecl",
+# "pnpm",
+# "podman",
+# "port",
+ "systemctl",
+ "tmux",
+# "yarn",
+]
+
+## Set commands that should be totally stripped and ignored from stats
+# common_prefix = ["sudo"]
+
+## Set commands that will be completely ignored from stats
+ignored_commands = [
+# "cd",
+ "ls",
+# "vi"
+]
+
+[keys]
+# Defaults to true. If disabled, using the up/down key won't exit the TUI when scrolled past the first/last entry.
+# scroll_exits = false
diff --git a/ar/.config/auto-cpufreq/auto-cpufreq.conf b/ar/.config/auto-cpufreq/auto-cpufreq.conf
new file mode 100644
index 0000000..ff40d0f
--- /dev/null
+++ b/ar/.config/auto-cpufreq/auto-cpufreq.conf
@@ -0,0 +1,61 @@
+# settings for when connected to a power source
+[charger]
+# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
+# preferred governor.
+governor = performance
+
+# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
+energy_performance_preference = performance
+
+# minimum cpu frequency (in kHz)
+# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000
+# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
+# to use this feature, uncomment the following line and set the value accordingly
+# scaling_min_freq = 800000
+
+# maximum cpu frequency (in kHz)
+# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000
+# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
+# to use this feature, uncomment the following line and set the value accordingly
+# scaling_max_freq = 1000000
+
+# turbo boost setting. possible values: always, auto, never
+turbo = auto
+
+# settings for when using battery power
+[battery]
+# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
+# preferred governor
+governor = powersave
+
+# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
+energy_performance_preference = power
+
+# minimum cpu frequency (in kHz)
+# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000
+# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
+# to use this feature, uncomment the following line and set the value accordingly
+# scaling_min_freq = 800000
+
+# maximum cpu frequency (in kHz)
+# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
+# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000
+# to use this feature, uncomment the following line and set the value accordingly
+# scaling_max_freq = 1000000
+
+# turbo boost setting. possible values: always, auto, never
+turbo = auto
+
+# experimental
+
+# Add battery charging threshold (currently only available to Lenovo)
+# checkout README.md for more info
+
+# enable thresholds true or false
+#enable_thresholds = true
+#
+# start threshold (0 is off ) can be 0-99
+#start_threshold = 50
+#
+# stop threshold (100 is off) can be 1-100
+#stop_threshold = 75
diff --git a/ar/.config/bash/bash_profile b/ar/.config/bash/bash_profile
new file mode 100644
index 0000000..068e3f5
--- /dev/null
+++ b/ar/.config/bash/bash_profile
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+umask 022
+
+#######################################################
+# EXPORTS
+#######################################################
+
+export XDG_CACHE_HOME="$HOME/.cache"
+export XDG_CONFIG_HOME="$HOME/.config"
+export XDG_DATA_HOME="$HOME/.local/share"
+export XDG_STATE_HOME="$HOME/.local/state"
+export CLICOLOR=1
+export EDITOR="vim"
+export HISTFILE="${XDG_DATA_HOME:-${HOME}/.local/share}/history/sh_history"
+export INPUTRC="${XDG_CONFIG_HOME:-${HOME}/.config}/shell/inputrc"
+export LESS="R"
+export LESS_TERMCAP_mb="$(printf '%b' '')"
+export LESS_TERMCAP_md="$(printf '%b' '')"
+export LESS_TERMCAP_me="$(printf '%b' '')"
+export LESS_TERMCAP_so="$(printf '%b' '')"
+export LESS_TERMCAP_se="$(printf '%b' '')"
+export LESS_TERMCAP_us="$(printf '%b' '')"
+export LESS_TERMCAP_ue="$(printf '%b' '')"
+export LESSOPEN="| /usr/bin/highlight -O ansi %s 2>/dev/null"
+export LS_COLORS="no=00:fi=00:di=00;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:*.xml=00;31:"
+export LS_OPTIONS="--color=auto"
+export TERM="xterm-256color"
+export GVIMINIT='let $MYGVIMRC="$XDG_CONFIG_HOME/vim/gvimrc" | source $MYGVIMRC'
+export VIMINIT='let $MYVIMRC="$XDG_CONFIG_HOME/vim/vimrc" | source $MYVIMRC'
+export VISUAL=$EDITOR
+
+#######################################################
+# Source global/local definitions
+#######################################################
+
+[ -f /etc/bash_completion ] && . /etc/bash_completion
+[ -f /usr/share/bash-completion/bash_completion ] && . /usr/share/bash-completion/bash_completion
+[ -f /etc/bash/bashrc ] && . /etc/bash/bashrc
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/bash/bashrc" ] && . "${XDG_CONFIG_HOME:-${HOME}/.config}/bash/bashrc"
diff --git a/ar/.config/bash/bashrc b/ar/.config/bash/bashrc
new file mode 100644
index 0000000..6ea1098
--- /dev/null
+++ b/ar/.config/bash/bashrc
@@ -0,0 +1,703 @@
+#!/bin/bash
+
+[[ $- != *i* ]] && return
+
+#######################################################
+# SET OPTIONS
+#######################################################
+# history
+HISTCONTROL=erasedups:ignoredups:ignorespace
+HISTFILESIZE=10000
+HISTSIZE=500
+
+# Allow ctrl-S for history navigation (with ctrl-R)
+stty -ixon
+PROMPT_COMMAND="history -a"
+unset GREP_OPTIONS
+
+set -o vi
+shopt -s autocd # goto without cd
+shopt -s direxpand # expend directory name
+shopt -s cdspell # ignore case cd
+shopt -s checkwinsize # Check the window size after each command and, if necessary, update the values of LINES and COLUMNS
+shopt -s histappend # Causes bash to append to history instead of overwriting it so if you start a new terminal, you have old session history
+
+# Completion settings
+# Ignore case on auto-completion
+# Note: bind used instead of sticking these in .inputrc
+bind "set completion-ignore-case on"
+# Show auto-completion list automatically, without double tab
+bind "set show-all-if-ambiguous on"
+
+#######################################################
+# MACHINE SPECIFIC ALIASES
+#######################################################
+
+alias grep="/usr/bin/grep $GREP_OPTIONS"
+
+# SSH
+# alias SERVERNAME="ssh YOURWEBSITE.com -l USERNAME -p PORTNUMBERHERE"
+alias \
+ nano="edit" \
+ pico="edit" \
+ snano="sedit" \
+ spico="sedit"
+
+# mount ISO files
+# mount -o loop /home/NAMEOFISO.iso /home/ISOMOUNTDIR/
+# umount /home/NAMEOFISO.iso
+# (Both commands done as root only.)
+
+#######################################################
+# GENERAL ALIASES
+#######################################################
+
+# Bash
+alias \
+ sbp="source ~/.config/bash/bash_profile" \
+ sbs="source ~/.config/bash/bashrc"
+
+# Prosody
+alias p="prosodyctl"
+
+# Systemctl
+alias s="systemctl"
+
+# Journalctl
+alias j="journalctl -xe"
+
+# nextcloud
+alias occ="sudo -u www-data php /var/www/nextcloud/occ"
+
+# alias to show the date
+alias da="date '+%Y-%m-%d %A %T %Z'"
+
+# modified commands
+alias \
+ cp="cp -i" \
+ freshclam="sudo freshclam" \
+ less="less -R" \
+ mkdir="mkdir -p" \
+ multitail="multitail --no-repeat -c" \
+ mv="mv -i" \
+ ping="ping -c 10" \
+ ps="ps auxf" \
+ rm="rm -iv" \
+ svi="sudo vi" \
+ v="vim" \
+ vi="vim" \
+ vis="vim '+set si'"
+
+# Go back
+alias \
+ ...='../..' \
+ ....='../../..' \
+ .....='../../../..'
+
+# Remove a directory and all files
+alias rmd="/bin/rm --recursive --force --verbose "
+
+# multiple directory listing commands
+alias \
+ l="/usr/bin/ls -h --color=always --group-directories-first" \
+ la="/usr/bin/ls -alh --color=always --group-directories-first" \
+ ll="/usr/bin/ls -Fls --color=always --group-directories-first" \
+ lla="/usr/bin/ls -aFls --color=always --group-directories-first" \
+ lm="/usr/bin/ls -alh --color=always |more --group-directories-first" \
+ lr="/usr/bin/ls -hlR --color=always --group-directories-first" \
+ ls="/usr/bin/ls -AFh --color=always --group-directories-first" \
+ lsa="/usr/bin/ls -hlru --color=always --group-directories-first" \
+ lsc="/usr/bin/ls -hclr --color=always --group-directories-first" \
+ lsd="/usr/bin/ls -l --color=always | egrep '^d'" \
+ lsda="/usr/bin/ls -la --color=always | egrep '^d'" \
+ lse="/usr/bin/ls -BhlX --color=always --group-directories-first" \
+ lsf="/usr/bin/ls -l --color=always | egrep -v '^d'" \
+ lsfa="/usr/bin/ls -la --color=always | egrep -v '^d'" \
+ lsm="/usr/bin/ls -hlr --time=mtime --color=always --group-directories-first" \
+ lsn="/usr/bin/ls -alp --color=always --group-directories-first" \
+ lss="/usr/bin/ls -hlrS --color=always --group-directories-first" \
+ lst="/usr/bin/ls -hlrt --color=always --group-directories-first" \
+ lw="/usr/bin/ls -Ahx --color=always --group-directories-first"
+
+# alias chmod commands
+alias \
+ cx="chmod a+x" \
+ 000="chmod -R 000" \
+ 644="chmod -R 644" \
+ 666="chmod -R 666" \
+ 755="chmod -R 755" \
+ 777="chmod -R 777"
+
+# Search command line history
+alias h="history | grep "
+
+# Search running processes
+alias \
+ ps="ps auxf" \
+ psg="ps auxf | grep " \
+ topcpu="/bin/ps -eo pcpu,pid,user,args | sort -k 1 -r | head -10"
+
+# Search files in the current folder
+alias f="find . | grep "
+
+# Count all files (recursively) in the current folder
+alias countfiles="for t in files links directories; do echo \`find . -type \${t:0:1} | wc -l\` \$t; done 2> /dev/null"
+
+# To see if a command is aliased, a file, or a built-in command
+alias checkcommand="type -t"
+
+# Show current network connections to the server
+alias ipview="netstat -anpl | grep :80 | awk '{print $5}' | cut -d':' -f1 | sort | uniq -c | sort -n | sed -e 's/^ *//' -e 's/ *$//'"
+
+# Show open ports
+alias openports="netstat -nape --inet"
+
+# Alias for safe and forced reboots
+alias \
+ rebootsafe="sudo shutdown -r now" \
+ rebootforce="sudo shutdown -r -n now"
+
+# Alias to show disk space and space used in a folder
+alias \
+ diskspace="du -S | sort -n -r |more" \
+ folders="du -h --max-depth=1" \
+ folderssort="find . -maxdepth 1 -type d -print0 | xargs -0 du -sk | sort -rn" \
+ tree="tree -CAhF --dirsfirst" \
+ treed="tree -CAFd" \
+ mountedinfo="df -hT"
+
+# Alias for archives
+alias \
+ mktar="tar -cvf" \
+ mkbz2="tar -cvjf" \
+ mkgz="tar -cvzf" \
+ untar="tar -xvf" \
+ unbz2="tar -xvjf" \
+ ungz="tar -xvzf"
+
+# Show all logs in /var/log
+alias logs="sudo find /var/log -type f -exec file {} \; | grep 'text' | cut -d' ' -f1 | sed -e's/:$//g' | grep -v '[0-9]$' | xargs tail -f"
+
+# SHA1
+alias sha1="openssl sha1"
+
+# Shell
+alias \
+ tobash="sudo chsh $USER -s /bin/bash && 'Now log out.'" \
+ tozsh="sudo chsh $USER -s /bin/zsh && 'Now log out.'" \
+ tofish="sudo chsh $USER -s /bin/fish && 'Now log out.'"
+
+# VIMRC
+alias \
+ v="vim" \
+ vi="vim"
+
+# wget
+alias wget=wget --hsts-file="~/.cache/wget-hsts"
+
+# rsync
+alias \
+ rsc="rsync -vrazPlu" \
+ rscd="rsync -vrazPlu --delete" \
+ rscr="rsync -vrazPlu --remove-source-files"
+
+# tor
+alias torh="cat /var/lib/tor/hidden_service/hostname"
+
+# nginx
+alias ngx="cd /etc/nginx"
+
+#######################################################
+# SPECIAL FUNCTIONS
+#######################################################
+
+# lfcd
+lfcd() {
+ tmp="$(mktemp -uq)"
+ trap 'rm -f $tmp >/dev/null 2>&1 && trap - HUP INT QUIT TERM PWR EXIT' HUP INT QUIT TERM PWR EXIT
+ lf -last-dir-path="$tmp" "$@"
+ if [ -f "$tmp" ]; then
+ dir="$(cat "$tmp")"
+ [ -d "$dir" ] && [ "$dir" != "$(pwd)" ] && cd "$dir"
+ fi
+}
+
+# Use the best version of pico installed
+edit() {
+ if [ "$(type -t jpico)" = "file" ]; then
+ # Use JOE text editor http://joe-editor.sourceforge.net/
+ jpico -nonotice -linums -nobackups "$@"
+ elif [ "$(type -t nano)" = "file" ]; then
+ nano -c "$@"
+ elif [ "$(type -t pico)" = "file" ]; then
+ pico "$@"
+ else
+ vim "$@"
+ fi
+}
+
+sedit() {
+ if [ "$(type -t jpico)" = "file" ]; then
+ # Use JOE text editor http://joe-editor.sourceforge.net/
+ sudo jpico -nonotice -linums -nobackups "$@"
+ elif [ "$(type -t nano)" = "file" ]; then
+ sudo nano -c "$@"
+ elif [ "$(type -t pico)" = "file" ]; then
+ sudo pico "$@"
+ else
+ sudo vim "$@"
+ fi
+}
+
+# Extracts any archive(s) (if unp isn't installed)
+extract() {
+ for archive in $*; do
+ if [ -f $archive ]; then
+ case $archive in
+ *.tar.bz2) tar xvjf $archive ;;
+ *.tar.gz) tar xvzf $archive ;;
+ *.bz2) bunzip2 $archive ;;
+ *.rar) rar x $archive ;;
+ *.gz) gunzip $archive ;;
+ *.tar) tar xvf $archive ;;
+ *.tbz2) tar xvjf $archive ;;
+ *.tgz) tar xvzf $archive ;;
+ *.zip) unzip $archive ;;
+ *.Z) uncompress $archive ;;
+ *.7z) 7z x $archive ;;
+ *) echo "don't know how to extract '$archive'..." ;;
+ esac
+ else
+ echo "'$archive' is not a valid file!"
+ fi
+ done
+}
+
+# Searches for text in all files in the current folder
+ftext() {
+ # -i case-insensitive
+ # -I ignore binary files
+ # -H causes filename to be printed
+ # -r recursive search
+ # -n causes line number to be printed
+ # optional: -F treat search term as a literal, not a regular expression
+ # optional: -l only print filenames and not the matching lines ex. grep -irl "$1" *
+ grep -iIHrn --color=always "$1" . | less -r
+}
+
+# Copy file with a progress bar
+cpf() {
+ set -e
+ strace -q -ewrite cp -- "${1}" "${2}" 2>&1 |
+ awk '{
+ count += $NF
+ if (count % 10 == 0) {
+ percent = count / total_size * 100
+ printf "%3d%% [", percent
+ for (i=0;i<=percent;i++)
+ printf "="
+ printf ">"
+ for (i=percent;i<100;i++)
+ printf " "
+ printf "]\r"
+ }
+ }
+ END { print "" }' total_size=$(stat -c '%s' "${1}") count=0
+}
+
+# Copy and go to the directory
+cpg() {
+ if [ -d "$2" ]; then
+ cp $1 $2 && cd $2
+ else
+ cp $1 $2
+ fi
+}
+
+# Move and go to the directory
+mvg() {
+ if [ -d "$2" ]; then
+ mv $1 $2 && cd $2
+ else
+ mv $1 $2
+ fi
+}
+
+# Create and go to the directory
+mc() {
+ mkdir -p $1 && cd $1
+}
+
+# Goes up a specified number of directories (i.e. up 4)
+up() {
+ local d=""
+ limit=$1
+ for ((i = 1; i <= limit; i++)); do
+ d=$d/..
+ done
+ d=$(echo $d | sed 's/^\///')
+ if [ -z "$d" ]; then
+ d=..
+ fi
+ cd $d
+}
+
+#Automatically do an ls after each cd
+# cd () {
+# if [ -n "$1" ]; then
+# builtin cd "$@" && ls
+# else
+# builtin cd ~ && ls
+# fi
+# }
+
+# Returns the last 2 fields of the working directory
+pwdtail() {
+ pwd | awk -F/ '{nlast = NF -1;print $nlast"/"$NF}'
+}
+
+# Show the current distribution
+distribution() {
+ local dtype
+ # Assume unknown
+ dtype="unknown"
+
+ # First test against Fedora / RHEL / CentOS / generic Redhat derivative
+ if [ -r /etc/rc.d/init.d/functions ]; then
+ source /etc/rc.d/init.d/functions
+ [ zz$(type -t passed 2>/dev/null) == "zzfunction" ] && dtype="redhat"
+
+ # Then test against SUSE (must be after Redhat,
+ # I've seen rc.status on Ubuntu I think? TODO: Recheck that)
+ elif [ -r /etc/rc.status ]; then
+ source /etc/rc.status
+ [ zz$(type -t rc_reset 2>/dev/null) == "zzfunction" ] && dtype="suse"
+
+ # Then test against Debian, Ubuntu and friends
+ elif [ -r /lib/lsb/init-functions ]; then
+ source /lib/lsb/init-functions
+ [ zz$(type -t log_begin_msg 2>/dev/null) == "zzfunction" ] && dtype="debian"
+
+ # Then test against Gentoo
+ elif [ -r /etc/init.d/functions.sh ]; then
+ source /etc/init.d/functions.sh
+ [ zz$(type -t ebegin 2>/dev/null) == "zzfunction" ] && dtype="gentoo"
+
+ # For Mandriva we currently just test if /etc/mandriva-release exists
+ # and isn't empty (TODO: Find a better way :)
+ elif [ -s /etc/mandriva-release ]; then
+ dtype="mandriva"
+
+ # For Slackware we currently just test if /etc/slackware-version exists
+ elif [ -s /etc/slackware-version ]; then
+ dtype="slackware"
+
+ fi
+ echo $dtype
+}
+
+# Show the current version of the operating system
+ver() {
+ local dtype
+ dtype=$(distribution)
+
+ if [ $dtype == "redhat" ]; then
+ if [ -s /etc/redhat-release ]; then
+ cat /etc/redhat-release && uname -a
+ else
+ cat /etc/issue && uname -a
+ fi
+ elif [ $dtype == "suse" ]; then
+ cat /etc/SuSE-release
+ elif [ $dtype == "debian" ]; then
+ lsb_release -a
+ # sudo cat /etc/issue && sudo cat /etc/issue.net && sudo cat /etc/lsb_release && sudo cat /etc/os-release # Linux Mint option 2
+ elif [ $dtype == "gentoo" ]; then
+ cat /etc/gentoo-release
+ elif [ $dtype == "mandriva" ]; then
+ cat /etc/mandriva-release
+ elif [ $dtype == "slackware" ]; then
+ cat /etc/slackware-version
+ else
+ if [ -s /etc/issue ]; then
+ cat /etc/issue
+ else
+ echo "Error: Unknown distribution"
+ exit 1
+ fi
+ fi
+}
+
+# Automatically install the needed support files for this .bashrc file
+install_bashrc_support() {
+ local dtype
+ dtype=$(distribution)
+
+ if [ $dtype == "redhat" ]; then
+ sudo yum install multitail tree joe
+ elif [ $dtype == "suse" ]; then
+ sudo zypper install multitail
+ sudo zypper install tree
+ sudo zypper install joe
+ elif [ $dtype == "debian" ]; then
+ sudo apt-get install multitail tree joe
+ elif [ $dtype == "gentoo" ]; then
+ sudo emerge multitail
+ sudo emerge tree
+ sudo emerge joe
+ elif [ $dtype == "mandriva" ]; then
+ sudo urpmi multitail
+ sudo urpmi tree
+ sudo urpmi joe
+ elif [ $dtype == "slackware" ]; then
+ echo "No install support for Slackware"
+ else
+ echo "Unknown distribution"
+ fi
+}
+
+# Show current network information
+netinfo() {
+ echo "--------------- Network Information ---------------"
+ /sbin/ifconfig | awk /'inet addr/ {print $2}'
+ echo ""
+ /sbin/ifconfig | awk /'Bcast/ {print $3}'
+ echo ""
+ /sbin/ifconfig | awk /'inet addr/ {print $4}'
+
+ /sbin/ifconfig | awk /'HWaddr/ {print $4,$5}'
+ echo "---------------------------------------------------"
+}
+
+# IP address lookup
+alias whatismyip="whatsmyip"
+function whatsmyip() {
+ # Dumps a list of all IP addresses for every device
+ # /sbin/ifconfig |grep -B1 "inet addr" |awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' |awk -F: '{ print $1 ": " $3 }';
+
+ # Internal IP Lookup
+ echo -n "Internal IP: "
+ /sbin/ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}'
+
+ # External IP Lookup
+ echo -n "External IP: "
+ wget http://smart-ip.net/myip -O - -q
+}
+
+# View Apache logs
+apachelog() {
+ if [ -f /etc/httpd/conf/httpd.conf ]; then
+ cd /var/log/httpd && ls -xAh && multitail --no-repeat -c -s 2 /var/log/httpd/*_log
+ else
+ cd /var/log/apache2 && ls -xAh && multitail --no-repeat -c -s 2 /var/log/apache2/*.log
+ fi
+}
+
+# Edit the Apache configuration
+apacheconfig() {
+ if [ -f /etc/httpd/conf/httpd.conf ]; then
+ sedit /etc/httpd/conf/httpd.conf
+ elif [ -f /etc/apache2/apache2.conf ]; then
+ sedit /etc/apache2/apache2.conf
+ else
+ echo "Error: Apache config file could not be found."
+ echo "Searching for possible locations:"
+ sudo updatedb && locate httpd.conf && locate apache2.conf
+ fi
+}
+
+# Edit the PHP configuration file
+phpconfig() {
+ if [ -f /etc/php.ini ]; then
+ sedit /etc/php.ini
+ elif [ -f /etc/php/php.ini ]; then
+ sedit /etc/php/php.ini
+ elif [ -f /etc/php5/php.ini ]; then
+ sedit /etc/php5/php.ini
+ elif [ -f /usr/bin/php5/bin/php.ini ]; then
+ sedit /usr/bin/php5/bin/php.ini
+ elif [ -f /etc/php5/apache2/php.ini ]; then
+ sedit /etc/php5/apache2/php.ini
+ else
+ echo "Error: php.ini file could not be found."
+ echo "Searching for possible locations:"
+ sudo updatedb && locate php.ini
+ fi
+}
+
+# Edit the MySQL configuration file
+mysqlconfig() {
+ if [ -f /etc/my.cnf ]; then
+ sedit /etc/my.cnf
+ elif [ -f /etc/mysql/my.cnf ]; then
+ sedit /etc/mysql/my.cnf
+ elif [ -f /usr/local/etc/my.cnf ]; then
+ sedit /usr/local/etc/my.cnf
+ elif [ -f /usr/bin/mysql/my.cnf ]; then
+ sedit /usr/bin/mysql/my.cnf
+ elif [ -f ~/my.cnf ]; then
+ sedit ~/my.cnf
+ elif [ -f ~/.my.cnf ]; then
+ sedit ~/.my.cnf
+ else
+ echo "Error: my.cnf file could not be found."
+ echo "Searching for possible locations:"
+ sudo updatedb && locate my.cnf
+ fi
+}
+
+# For some reason, rot13 pops up everywhere
+rot13() {
+ if [ $# -eq 0 ]; then
+ tr '[a-m][n-z][A-M][N-Z]' '[n-z][a-m][N-Z][A-M]'
+ else
+ echo $* | tr '[a-m][n-z][A-M][N-Z]' '[n-z][a-m][N-Z][A-M]'
+ fi
+}
+
+# Trim leading and trailing spaces (for scripts)
+trim() {
+ local var=$@
+ var='${var#"${var%%[![:space:]]*}"}' # remove leading whitespace characters
+ var='${var%"${var##*[![:space:]]}"}' # remove trailing whitespace characters
+ echo -n "$var"
+}
+
+#######################################################
+# Set command prompt
+#######################################################
+alias cpu="grep 'cpu ' /proc/stat | awk '{usage=(\$2+\$4)*100/(\$2+\$4+\$5)} END {print usage}' | awk '{printf(\"%.1f\n\", \$1)}'"
+function __setprompt {
+ local LAST_COMMAND=$? # Must come first!
+
+ # Define colors
+ local LIGHTGRAY="\033[0;37m"
+ local WHITE="\033[1;37m"
+ local BLACK="\033[0;30m"
+ local DARKGRAY="\033[1;30m"
+ local RED="\033[0;31m"
+ local LIGHTRED="\033[1;31m"
+ local GREEN="\033[0;32m"
+ local LIGHTGREEN="\033[1;32m"
+ local BROWN="\033[0;33m"
+ local YELLOW="\033[1;33m"
+ local BLUE="\033[0;34m"
+ local LIGHTBLUE="\033[1;34m"
+ local MAGENTA="\033[0;35m"
+ local LIGHTMAGENTA="\033[1;35m"
+ local CYAN="\033[0;36m"
+ local LIGHTCYAN="\033[1;36m"
+ local NOCOLOR="\033[0m"
+
+ # Show error exit code if there is one
+ if [[ $LAST_COMMAND != 0 ]]; then
+ # PS1="\[${RED}\](\[${LIGHTRED}\]ERROR\[${RED}\])-(\[${LIGHTRED}\]Exit Code \[${WHITE}\]${LAST_COMMAND}\[${RED}\])-(\[${LIGHTRED}\]"
+ PS1="\[${DARKGRAY}\](\[${LIGHTRED}\]ERROR\[${DARKGRAY}\])-(\[${RED}\]Exit Code \[${LIGHTRED}\]${LAST_COMMAND}\[${DARKGRAY}\])-(\[${RED}\]"
+ if [[ $LAST_COMMAND == 1 ]]; then
+ PS1+="General error"
+ elif [ $LAST_COMMAND == 2 ]; then
+ PS1+="Missing keyword, command, or permission problem"
+ elif [ $LAST_COMMAND == 126 ]; then
+ PS1+="Permission problem or command is not an executable"
+ elif [ $LAST_COMMAND == 127 ]; then
+ PS1+="Command not found"
+ elif [ $LAST_COMMAND == 128 ]; then
+ PS1+="Invalid argument to exit"
+ elif [ $LAST_COMMAND == 129 ]; then
+ PS1+="Fatal error signal 1"
+ elif [ $LAST_COMMAND == 130 ]; then
+ PS1+="Script terminated by Control-C"
+ elif [ $LAST_COMMAND == 131 ]; then
+ PS1+="Fatal error signal 3"
+ elif [ $LAST_COMMAND == 132 ]; then
+ PS1+="Fatal error signal 4"
+ elif [ $LAST_COMMAND == 133 ]; then
+ PS1+="Fatal error signal 5"
+ elif [ $LAST_COMMAND == 134 ]; then
+ PS1+="Fatal error signal 6"
+ elif [ $LAST_COMMAND == 135 ]; then
+ PS1+="Fatal error signal 7"
+ elif [ $LAST_COMMAND == 136 ]; then
+ PS1+="Fatal error signal 8"
+ elif [ $LAST_COMMAND == 137 ]; then
+ PS1+="Fatal error signal 9"
+ elif [ $LAST_COMMAND -gt 255 ]; then
+ PS1+="Exit status out of range"
+ else
+ PS1+="Unknown error code"
+ fi
+ PS1+="\[${DARKGRAY}\])\[${NOCOLOR}\]\n"
+ else
+ PS1=""
+ fi
+
+ # Date
+ PS1+="\[${DARKGRAY}\](\[${CYAN}\]\$(date +%a) $(date +%b-'%-m')" # Date
+ PS1+="${BLUE} $(date +'%-I':%M:%S%P)\[${DARKGRAY}\])-" # Time
+
+ # CPU
+ PS1+="(\[${MAGENTA}\]CPU $(cpu)%"
+
+ # Jobs
+ PS1+="\[${DARKGRAY}\]:\[${MAGENTA}\]\j"
+
+ # Network Connections (for a server - comment out for non-server)
+ PS1+="\[${DARKGRAY}\]:\[${MAGENTA}\]Net $(awk 'END {print NR}' /proc/net/tcp)"
+
+ PS1+="\[${DARKGRAY}\])-"
+
+ # User and server
+ local SSH_IP=$(echo $SSH_CLIENT | awk '{ print $1 }')
+ local SSH2_IP=$(echo $SSH2_CLIENT | awk '{ print $1 }')
+ if [ $SSH2_IP ] || [ $SSH_IP ]; then
+ PS1+="(\[${RED}\]\u@\h"
+ else
+ PS1+="(\[${RED}\]\u"
+ fi
+
+ # Current directory
+ PS1+="\[${DARKGRAY}\]:\[${BROWN}\]\w\[${DARKGRAY}\])-"
+
+ # Total size of files in current directory
+ PS1+="(\[${GREEN}\]$(/bin/ls -lah | /bin/grep -m 1 total | /bin/sed 's/total //')\[${DARKGRAY}\]:"
+
+ # Number of files
+ PS1+="\[${GREEN}\]\$(/bin/ls -A -1 | /usr/bin/wc -l)\[${DARKGRAY}\])"
+
+ # Skip to the next line
+ PS1+="\n"
+
+ if [[ $EUID -ne 0 ]]; then
+ PS1+="\[${GREEN}\]>\[${NOCOLOR}\] " # Normal user
+ else
+ PS1+="\[${RED}\]>\[${NOCOLOR}\] " # Root user
+ fi
+
+ # PS2 is used to continue a command using the \ character
+ PS2="\[${DARKGRAY}\]>\[${NOCOLOR}\] "
+
+ # PS3 is used to enter a number choice in a script
+ PS3='Please enter a number from above list: '
+
+ # PS4 is used for tracing a script in debug mode
+ PS4='\[${DARKGRAY}\]+\[${NOCOLOR}\] '
+}
+# PROMPT_COMMAND='__setprompt'
+PS1="\[\e[1m\]\[\e[31m\][\[\e[33m\]\u\[\e[32m\]@\[\e[34m\]\h \[\e[35m\]\W\[\e[31m\]]\[\e[37m\]\\$ \[\e[0m\]"
+
+#######################################################
+# KEY BINDING
+#######################################################
+
+bind '"\C-l":clear-screen'
+bind '"\C-g":"lfcd\n"'
+
+#######################################################
+# SOURCE
+#######################################################
+
+eval "$(dircolors)"
+
+[ -f "${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/bash-preexec" ] && . "${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/bash-preexec"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/rootshortcutrc" ] && . "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/rootshortcutrc"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/rootzshnameddirrc" ] && . "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/rootzshnameddirrc"
diff --git a/ar/.config/bat/config b/ar/.config/bat/config
new file mode 100644
index 0000000..38c44f2
--- /dev/null
+++ b/ar/.config/bat/config
@@ -0,0 +1,8 @@
+--theme="ansi"
+# --theme="Catppuccin Frappe"
+# --theme="Catppuccin Mocha"
+# --theme="gruvbox-dark"
+# --theme="base16-256"
+# --theme="TwoDark"
+# --theme="Visual Studio Dark+"
+# --theme="Dracula"
diff --git a/ar/.config/bat/themes/Catppuccin Frappe.tmTheme b/ar/.config/bat/themes/Catppuccin Frappe.tmTheme
new file mode 100644
index 0000000..ffb7cba
--- /dev/null
+++ b/ar/.config/bat/themes/Catppuccin Frappe.tmTheme
@@ -0,0 +1,2059 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>name</key>
+ <string>Catppuccin Frappé</string>
+ <key>semanticClass</key>
+ <string>theme.dark.catppuccin-frappé</string>
+ <key>uuid</key>
+ <string>e0ada983-8938-490c-86f0-97a1a0ec58e4</string>
+ <key>author</key>
+ <string>Catppuccin Org</string>
+ <key>colorSpaceName</key>
+ <string>sRGB</string>
+ <key>settings</key>
+ <array>
+ <dict>
+ <key>settings</key>
+ <dict>
+ <key>background</key>
+ <string>#303446</string>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ <key>caret</key>
+ <string>#f2d5cf</string>
+ <key>lineHighlight</key>
+ <string>#414559</string>
+ <key>misspelling</key>
+ <string>#e78284</string>
+ <key>accent</key>
+ <string>#ca9ee6</string>
+ <key>selection</key>
+ <string>#949cbb40</string>
+ <key>activeGuide</key>
+ <string>#51576d</string>
+ <key>findHighlight</key>
+ <string>#506373</string>
+ <key>gutterForeground</key>
+ <string>#838ba7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Basic text &amp; variable names (incl. leading punctuation)</string>
+ <key>scope</key>
+ <string>text, source, variable.other.readwrite, punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Parentheses, Brackets, Braces</string>
+ <key>scope</key>
+ <string>punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#949cbb</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comments</string>
+ <key>scope</key>
+ <string>comment, punctuation.definition.comment</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#737994</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>string, punctuation.definition.string</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>constant.character.escape</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Booleans, constants, numbers</string>
+ <key>scope</key>
+ <string>constant.numeric, variable.other.constant, entity.name.constant, constant.language.boolean, constant.language.false, constant.language.true, keyword.other.unit.user-defined, keyword.other.unit.suffix.floating-point</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>keyword, keyword.operator.word, keyword.operator.new, variable.language.super, support.type.primitive, storage.type, storage.modifier, punctuation.definition.keyword</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.tag.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Punctuation</string>
+ <key>scope</key>
+ <string>keyword.operator, punctuation.accessor, punctuation.definition.generic, meta.function.closure punctuation.section.parameters, punctuation.definition.tag, punctuation.separator.key-value</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.function, meta.function-call.method, support.function, support.function.misc, variable.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes</string>
+ <key>scope</key>
+ <string>entity.name.class, entity.other.inherited-class, support.class, meta.function-call.constructor, entity.name.struct</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum</string>
+ <key>scope</key>
+ <string>entity.name.enum</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum member</string>
+ <key>scope</key>
+ <string>meta.enum variable.other.readwrite, variable.other.enummember</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>meta.property.object</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Types</string>
+ <key>scope</key>
+ <string>meta.type, meta.type-alias, support.type, entity.name.type</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorators</string>
+ <key>scope</key>
+ <string>meta.annotation variable.function, meta.annotation variable.annotation.function, meta.annotation punctuation.definition.annotation, meta.decorator, punctuation.decorator</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter, meta.function.parameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Built-ins</string>
+ <key>scope</key>
+ <string>constant.language, support.function.builtin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.other.attribute-name.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Preprocessor directives</string>
+ <key>scope</key>
+ <string>keyword.control.directive, punctuation.definition.directive</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Type parameters</string>
+ <key>scope</key>
+ <string>punctuation.definition.typeparameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Namespaces</string>
+ <key>scope</key>
+ <string>entity.name.namespace</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Property names (left hand assignments in json/yaml/css)</string>
+ <key>scope</key>
+ <string>support.type.property-name.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>This/Self keyword</string>
+ <key>scope</key>
+ <string>variable.language.this, variable.language.this punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>variable.object.property</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>String template interpolation</string>
+ <key>scope</key>
+ <string>string.template variable, string variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>`new` as bold</string>
+ <key>scope</key>
+ <string>keyword.operator.new</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ extern keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.specifier.extern.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ scope resolution</string>
+ <key>scope</key>
+ <string>entity.name.scope-resolution.template.call.cpp, entity.name.scope-resolution.parameter.cpp, entity.name.scope-resolution.cpp, entity.name.scope-resolution.function.definition.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ doc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.doxygen</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ operators</string>
+ <key>scope</key>
+ <string>storage.modifier.reference.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# Interpolated Strings</string>
+ <key>scope</key>
+ <string>meta.interpolation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# xml-style docs</string>
+ <key>scope</key>
+ <string>comment.block.documentation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes, reflecting the className color in JSX</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.class.css, entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Operators</string>
+ <key>scope</key>
+ <string>punctuation.separator.operator.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Pseudo classes</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.pseudo-class</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css constant.other.unicode-range</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css variable.parameter.url</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS vendored property names</string>
+ <key>scope</key>
+ <string>support.type.vendored.property-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Less/SCSS right-hand variables (@/$-prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-value variable, source.css meta.property-value variable.other.less, source.css meta.property-value variable.other.less punctuation.definition.variable.less, meta.definition.variable.scss</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS variables (--prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-list variable, meta.property-list variable.other.less, meta.property-list variable.other.less punctuation.definition.variable.less</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Percentage values, styled the same as numbers</string>
+ <key>scope</key>
+ <string>keyword.other.unit.percentage.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Attribute selectors, styled the same as strings</string>
+ <key>scope</key>
+ <string>source.css meta.attribute-selector</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML keys, other left-hand assignments</string>
+ <key>scope</key>
+ <string>keyword.other.definition.ini, punctuation.support.type.property-name.json, support.type.property-name.json, punctuation.support.type.property-name.toml, support.type.property-name.toml, entity.name.tag.yaml, punctuation.support.type.property-name.yaml, support.type.property-name.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML constants</string>
+ <key>scope</key>
+ <string>constant.language.json, constant.language.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchors</string>
+ <key>scope</key>
+ <string>entity.name.type.anchor.yaml, variable.other.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML tables / ini groups</string>
+ <key>scope</key>
+ <string>support.type.property-name.table, entity.name.section.group-title.ini</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML dates</string>
+ <key>scope</key>
+ <string>constant.other.time.datetime.offset.toml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchor puctuation</string>
+ <key>scope</key>
+ <string>punctuation.definition.anchor.yaml, punctuation.definition.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML triple dashes</string>
+ <key>scope</key>
+ <string>entity.other.document.begin.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markup Diff</string>
+ <key>scope</key>
+ <string>markup.changed.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff</string>
+ <key>scope</key>
+ <string>meta.diff.header.from-file, meta.diff.header.to-file, punctuation.definition.from-file.diff, punctuation.definition.to-file.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Inserted</string>
+ <key>scope</key>
+ <string>markup.inserted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Deleted</string>
+ <key>scope</key>
+ <string>markup.deleted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv left-hand side assignments</string>
+ <key>scope</key>
+ <string>variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv reference to existing env variable</string>
+ <key>scope</key>
+ <string>string.quoted variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript functions</string>
+ <key>scope</key>
+ <string>support.function.builtin.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript constants</string>
+ <key>scope</key>
+ <string>constant.language.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comment keywords</string>
+ <key>scope</key>
+ <string>comment meta.annotation.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>go:embed, go:build, etc.</string>
+ <key>scope</key>
+ <string>comment meta.annotation.parameters.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Go constants (nil, true, false)</string>
+ <key>scope</key>
+ <string>constant.language.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL variables</string>
+ <key>scope</key>
+ <string>variable.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL aliases</string>
+ <key>scope</key>
+ <string>string.unquoted.alias.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eebebe</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL enum members</string>
+ <key>scope</key>
+ <string>constant.character.enum.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL field in types</string>
+ <key>scope</key>
+ <string>meta.objectvalues.graphql constant.object.key.graphql string.unquoted.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eebebe</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML DOCTYPE as keyword</string>
+ <key>scope</key>
+ <string>keyword.other.doctype, meta.tag.sgml.doctype punctuation.definition.tag, meta.tag.metadata.doctype entity.name.tag, meta.tag.metadata.doctype punctuation.definition.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML-like &lt;tags/&gt;</string>
+ <key>scope</key>
+ <string>entity.name.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Special characters like &amp;amp;</string>
+ <key>scope</key>
+ <string>text.html constant.character.entity, text.html constant.character.entity punctuation, constant.character.entity.xml, constant.character.entity.xml punctuation, constant.character.entity.js.jsx, constant.charactger.entity.js.jsx punctuation, constant.character.entity.tsx, constant.character.entity.tsx punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML tag attribute values</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Components</string>
+ <key>scope</key>
+ <string>support.class.component, support.class.component.jsx, support.class.component.tsx, support.class.component.vue</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Annotations</string>
+ <key>scope</key>
+ <string>punctuation.definition.annotation, storage.type.annotation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java enums</string>
+ <key>scope</key>
+ <string>constant.other.enum.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java imports</string>
+ <key>scope</key>
+ <string>storage.modifier.import.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Javadoc</string>
+ <key>scope</key>
+ <string>comment.block.javadoc.java keyword.other.documentation.javadoc.java</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Exported Variable</string>
+ <key>scope</key>
+ <string>meta.export variable.other.readwrite.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JS/TS constants &amp; properties</string>
+ <key>scope</key>
+ <string>variable.other.constant.js, variable.other.constant.ts, variable.other.property.js, variable.other.property.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc; these are mainly params, so styled as such</string>
+ <key>scope</key>
+ <string>variable.other.jsdoc, comment.block.documentation variable.other</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.jsdoc</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>support.type.object.console.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Node constants as keywords (module, etc.)</string>
+ <key>scope</key>
+ <string>support.constant.node, support.type.object.module.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>implements as keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.implements</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Builtin types</string>
+ <key>scope</key>
+ <string>constant.language.null.js, constant.language.null.ts, constant.language.undefined.js, constant.language.undefined.ts, support.type.builtin.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter.generic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arrow functions</string>
+ <key>scope</key>
+ <string>keyword.declaration.function.arrow.js, storage.type.function.arrow.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorator punctuations (decorators inherit from blue functions, instead of styleguide peach)</string>
+ <key>scope</key>
+ <string>punctuation.decorator.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Extra JS/TS keywords</string>
+ <key>scope</key>
+ <string>keyword.operator.expression.in.js, keyword.operator.expression.in.ts, keyword.operator.expression.infer.ts, keyword.operator.expression.instanceof.js, keyword.operator.expression.instanceof.ts, keyword.operator.expression.is, keyword.operator.expression.keyof.ts, keyword.operator.expression.of.js, keyword.operator.expression.of.ts, keyword.operator.expression.typeof.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia macros</string>
+ <key>scope</key>
+ <string>support.function.macro.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia language constants (true, false)</string>
+ <key>scope</key>
+ <string>constant.language.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia other constants (these seem to be arguments inside arrays)</string>
+ <key>scope</key>
+ <string>constant.other.symbol.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX preamble</string>
+ <key>scope</key>
+ <string>text.tex keyword.control.preamble</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX be functions</string>
+ <key>scope</key>
+ <string>text.tex support.function.be</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX math</string>
+ <key>scope</key>
+ <string>constant.other.general.math.tex</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eebebe</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring keywords</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua storage.type.annotation.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring variables</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua entity.name.variable.lua, comment.line.double-dash.documentation.lua variable.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.1.markdown punctuation.definition.heading.markdown, heading.1.markdown, markup.heading.atx.1.mdx, markup.heading.atx.1.mdx punctuation.definition.heading.mdx, markup.heading.setext.1.markdown, markup.heading.heading-0.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.2.markdown punctuation.definition.heading.markdown, heading.2.markdown, markup.heading.atx.2.mdx, markup.heading.atx.2.mdx punctuation.definition.heading.mdx, markup.heading.setext.2.markdown, markup.heading.heading-1.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.3.markdown punctuation.definition.heading.markdown, heading.3.markdown, markup.heading.atx.3.mdx, markup.heading.atx.3.mdx punctuation.definition.heading.mdx, markup.heading.heading-2.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.4.markdown punctuation.definition.heading.markdown, heading.4.markdown, markup.heading.atx.4.mdx, markup.heading.atx.4.mdx punctuation.definition.heading.mdx, markup.heading.heading-3.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.5.markdown punctuation.definition.heading.markdown, heading.5.markdown, markup.heading.atx.5.mdx, markup.heading.atx.5.mdx punctuation.definition.heading.mdx, markup.heading.heading-4.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.6.markdown punctuation.definition.heading.markdown, heading.6.markdown, markup.heading.atx.6.mdx, markup.heading.atx.6.mdx punctuation.definition.heading.mdx, markup.heading.heading-5.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.bold</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.italic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.strikethrough</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a5adce</string>
+ <key>fontStyle</key>
+ <string>strikethrough</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown auto links</string>
+ <key>scope</key>
+ <string>punctuation.definition.link, markup.underline.link</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown links</string>
+ <key>scope</key>
+ <string>text.html.markdown punctuation.definition.link.title, string.other.link.title.markdown, markup.link, punctuation.definition.constant.markdown, constant.other.reference.link.markdown, markup.substitution.attribute-reference</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#babbf1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown code spans</string>
+ <key>scope</key>
+ <string>punctuation.definition.raw.markdown, markup.inline.raw.string.markdown, markup.raw.block.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backtick language identifier</string>
+ <key>scope</key>
+ <string>fenced_code.block.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backticks</string>
+ <key>scope</key>
+ <string>markup.fenced_code.block punctuation.definition, markup.raw support.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#949cbb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown quotes</string>
+ <key>scope</key>
+ <string>markup.quote, punctuation.definition.quote.begin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown separators</string>
+ <key>scope</key>
+ <string>meta.separator.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown list bullets</string>
+ <key>scope</key>
+ <string>punctuation.definition.list.begin.markdown, markup.list.bullet</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix attribute names</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name.multipart.nix, entity.other.attribute-name.single.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix parameter names</string>
+ <key>scope</key>
+ <string>variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix interpolated parameter names</string>
+ <key>scope</key>
+ <string>meta.embedded variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#babbf1</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix paths</string>
+ <key>scope</key>
+ <string>string.unquoted.path.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Attributes</string>
+ <key>scope</key>
+ <string>support.attribute.builtin, meta.attribute.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Parameters (needed for the leading dollar sign)</string>
+ <key>scope</key>
+ <string>meta.function.parameters.php punctuation.definition.variable.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Constants (null, __FILE__, etc.)</string>
+ <key>scope</key>
+ <string>constant.language.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP functions</string>
+ <key>scope</key>
+ <string>text.html.php support.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHPdoc keywords</string>
+ <key>scope</key>
+ <string>keyword.other.phpdoc.php</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python argument functions reset to text, otherwise they inherit blue from function-call</string>
+ <key>scope</key>
+ <string>support.variable.magic.python, meta.function-call.arguments.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python double underscore functions</string>
+ <key>scope</key>
+ <string>support.function.magic.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python `self` keyword</string>
+ <key>scope</key>
+ <string>variable.parameter.function.language.special.self.python, variable.language.special.self.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python keyword flow/logical (for ... in)</string>
+ <key>scope</key>
+ <string>keyword.control.flow.python, keyword.operator.logical.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python storage type</string>
+ <key>scope</key>
+ <string>storage.type.function.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function support</string>
+ <key>scope</key>
+ <string>support.token.decorator.python, meta.function.decorator.identifier.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function calls</string>
+ <key>scope</key>
+ <string>meta.function-call.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function decorators</string>
+ <key>scope</key>
+ <string>entity.name.function.decorator.python, punctuation.definition.decorator.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python placeholder reset to normal string</string>
+ <key>scope</key>
+ <string>constant.character.format.placeholder.other.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python exception &amp; builtins such as exit()</string>
+ <key>scope</key>
+ <string>support.type.exception.python, support.function.builtin.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>entity.name.type</string>
+ <key>scope</key>
+ <string>support.type.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python constants (True/False)</string>
+ <key>scope</key>
+ <string>constant.language.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arguments accessed later in the function body</string>
+ <key>scope</key>
+ <string>meta.indexed-name.python, meta.item-access.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python f-strings/binary/unicode storage types</string>
+ <key>scope</key>
+ <string>storage.type.string.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python type hints</string>
+ <key>scope</key>
+ <string>meta.function.parameters.python</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex string begin/end in JS/TS</string>
+ <key>scope</key>
+ <string>string.regexp punctuation.definition.string.begin, string.regexp punctuation.definition.string.end</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex anchors (^, $)</string>
+ <key>scope</key>
+ <string>keyword.control.anchor.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex regular string match</string>
+ <key>scope</key>
+ <string>string.regexp.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex group parenthesis &amp; backreference (\1, \2, \3, ...)</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.regexp, keyword.other.back-reference.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6d189</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character class []</string>
+ <key>scope</key>
+ <string>punctuation.definition.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character classes (\d, \w, \s)</string>
+ <key>scope</key>
+ <string>constant.other.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex range</string>
+ <key>scope</key>
+ <string>constant.other.character-class.range.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f2d5cf</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex quantifier</string>
+ <key>scope</key>
+ <string>keyword.operator.quantifier.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex constant/numeric</string>
+ <key>scope</key>
+ <string>constant.character.numeric.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex lookaheads, negative lookaheads, lookbehinds, negative lookbehinds</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.no-capture.regexp, meta.assertion.look-ahead.regexp, meta.assertion.negative-look-ahead.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute</string>
+ <key>scope</key>
+ <string>meta.annotation.rust, meta.annotation.rust punctuation, meta.attribute.rust, punctuation.definition.attribute.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute strings</string>
+ <key>scope</key>
+ <string>meta.attribute.rust string.quoted.double.rust, meta.attribute.rust string.quoted.single.char.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust keyword</string>
+ <key>scope</key>
+ <string>entity.name.function.macro.rules.rust, storage.type.module.rust, storage.modifier.rust, storage.type.struct.rust, storage.type.enum.rust, storage.type.trait.rust, storage.type.union.rust, storage.type.impl.rust, storage.type.rust, storage.type.function.rust, storage.type.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust u/i32, u/i64, etc.</string>
+ <key>scope</key>
+ <string>entity.name.type.numeric.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust generic</string>
+ <key>scope</key>
+ <string>meta.generic.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust impl</string>
+ <key>scope</key>
+ <string>entity.name.impl.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust module</string>
+ <key>scope</key>
+ <string>entity.name.module.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust trait</string>
+ <key>scope</key>
+ <string>entity.name.trait.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust struct</string>
+ <key>scope</key>
+ <string>storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust union</string>
+ <key>scope</key>
+ <string>entity.name.union.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust enum member</string>
+ <key>scope</key>
+ <string>meta.enum.rust storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust macro</string>
+ <key>scope</key>
+ <string>support.macro.rust, meta.macro.rust support.function.rust, entity.name.function.macro.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust lifetime</string>
+ <key>scope</key>
+ <string>storage.modifier.lifetime.rust, entity.name.type.lifetime</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust string formatting</string>
+ <key>scope</key>
+ <string>string.quoted.double.rust constant.other.placeholder.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust return type generic</string>
+ <key>scope</key>
+ <string>meta.function.return-type.rust meta.generic.rust storage.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust functions</string>
+ <key>scope</key>
+ <string>meta.function.call.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust angle brackets</string>
+ <key>scope</key>
+ <string>punctuation.brackets.angle.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#99d1db</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust constants</string>
+ <key>scope</key>
+ <string>constant.other.caps.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust function parameters</string>
+ <key>scope</key>
+ <string>meta.function.definition.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea999c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust closure variables</string>
+ <key>scope</key>
+ <string>meta.function.call.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust self</string>
+ <key>scope</key>
+ <string>variable.language.self.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust metavariable names</string>
+ <key>scope</key>
+ <string>variable.other.metavariable.name.rust, meta.macro.metavariable.rust keyword.operator.macro.dollar.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang</string>
+ <key>scope</key>
+ <string>comment.line.shebang, comment.line.shebang punctuation.definition.comment, comment.line.shebang, punctuation.definition.comment.shebang.shell, meta.shebang.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang command</string>
+ <key>scope</key>
+ <string>comment.line.shebang constant.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command</string>
+ <key>scope</key>
+ <string>meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation, meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command variable</string>
+ <key>scope</key>
+ <string>meta.string meta.interpolation.parameter.shell variable.other.readwrite</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.shell punctuation.section.interpolation, punctuation.definition.evaluation.backticks.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell EOF</string>
+ <key>scope</key>
+ <string>entity.name.tag.heredoc.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell quoted variable</string>
+ <key>scope</key>
+ <string>string.quoted.double.shell variable.other.normal.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6d0f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.synopsis.man, markup.heading.title.man, markup.heading.other.man, markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ca9ee6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.commands.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8caaee</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4b8e4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Man page options</string>
+ <key>scope</key>
+ <string>entity.name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#81c8be</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.1.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e78284</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.2.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ef9f76</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e5c890</string>
+ </dict>
+ </dict>
+ </array>
+ </dict>
+</plist> \ No newline at end of file
diff --git a/ar/.config/bat/themes/Catppuccin Latte.tmTheme b/ar/.config/bat/themes/Catppuccin Latte.tmTheme
new file mode 100644
index 0000000..9bb706f
--- /dev/null
+++ b/ar/.config/bat/themes/Catppuccin Latte.tmTheme
@@ -0,0 +1,2059 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>name</key>
+ <string>Catppuccin Latte</string>
+ <key>semanticClass</key>
+ <string>theme.light.catppuccin-latte</string>
+ <key>uuid</key>
+ <string>96a262cd-4b2f-49f5-9125-8dd0077cbfe1</string>
+ <key>author</key>
+ <string>Catppuccin Org</string>
+ <key>colorSpaceName</key>
+ <string>sRGB</string>
+ <key>settings</key>
+ <array>
+ <dict>
+ <key>settings</key>
+ <dict>
+ <key>background</key>
+ <string>#eff1f5</string>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ <key>caret</key>
+ <string>#dc8a78</string>
+ <key>lineHighlight</key>
+ <string>#ccd0da</string>
+ <key>misspelling</key>
+ <string>#d20f39</string>
+ <key>accent</key>
+ <string>#8839ef</string>
+ <key>selection</key>
+ <string>#7c7f934d</string>
+ <key>activeGuide</key>
+ <string>#bcc0cc</string>
+ <key>findHighlight</key>
+ <string>#a9daf0</string>
+ <key>gutterForeground</key>
+ <string>#8c8fa1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Basic text &amp; variable names (incl. leading punctuation)</string>
+ <key>scope</key>
+ <string>text, source, variable.other.readwrite, punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Parentheses, Brackets, Braces</string>
+ <key>scope</key>
+ <string>punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#7c7f93</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comments</string>
+ <key>scope</key>
+ <string>comment, punctuation.definition.comment</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#9ca0b0</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>string, punctuation.definition.string</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>constant.character.escape</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Booleans, constants, numbers</string>
+ <key>scope</key>
+ <string>constant.numeric, variable.other.constant, entity.name.constant, constant.language.boolean, constant.language.false, constant.language.true, keyword.other.unit.user-defined, keyword.other.unit.suffix.floating-point</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>keyword, keyword.operator.word, keyword.operator.new, variable.language.super, support.type.primitive, storage.type, storage.modifier, punctuation.definition.keyword</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.tag.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Punctuation</string>
+ <key>scope</key>
+ <string>keyword.operator, punctuation.accessor, punctuation.definition.generic, meta.function.closure punctuation.section.parameters, punctuation.definition.tag, punctuation.separator.key-value</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.function, meta.function-call.method, support.function, support.function.misc, variable.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes</string>
+ <key>scope</key>
+ <string>entity.name.class, entity.other.inherited-class, support.class, meta.function-call.constructor, entity.name.struct</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum</string>
+ <key>scope</key>
+ <string>entity.name.enum</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum member</string>
+ <key>scope</key>
+ <string>meta.enum variable.other.readwrite, variable.other.enummember</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>meta.property.object</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Types</string>
+ <key>scope</key>
+ <string>meta.type, meta.type-alias, support.type, entity.name.type</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorators</string>
+ <key>scope</key>
+ <string>meta.annotation variable.function, meta.annotation variable.annotation.function, meta.annotation punctuation.definition.annotation, meta.decorator, punctuation.decorator</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter, meta.function.parameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Built-ins</string>
+ <key>scope</key>
+ <string>constant.language, support.function.builtin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.other.attribute-name.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Preprocessor directives</string>
+ <key>scope</key>
+ <string>keyword.control.directive, punctuation.definition.directive</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Type parameters</string>
+ <key>scope</key>
+ <string>punctuation.definition.typeparameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Namespaces</string>
+ <key>scope</key>
+ <string>entity.name.namespace</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Property names (left hand assignments in json/yaml/css)</string>
+ <key>scope</key>
+ <string>support.type.property-name.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>This/Self keyword</string>
+ <key>scope</key>
+ <string>variable.language.this, variable.language.this punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>variable.object.property</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>String template interpolation</string>
+ <key>scope</key>
+ <string>string.template variable, string variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>`new` as bold</string>
+ <key>scope</key>
+ <string>keyword.operator.new</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ extern keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.specifier.extern.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ scope resolution</string>
+ <key>scope</key>
+ <string>entity.name.scope-resolution.template.call.cpp, entity.name.scope-resolution.parameter.cpp, entity.name.scope-resolution.cpp, entity.name.scope-resolution.function.definition.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ doc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.doxygen</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ operators</string>
+ <key>scope</key>
+ <string>storage.modifier.reference.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# Interpolated Strings</string>
+ <key>scope</key>
+ <string>meta.interpolation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# xml-style docs</string>
+ <key>scope</key>
+ <string>comment.block.documentation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes, reflecting the className color in JSX</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.class.css, entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Operators</string>
+ <key>scope</key>
+ <string>punctuation.separator.operator.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Pseudo classes</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.pseudo-class</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css constant.other.unicode-range</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css variable.parameter.url</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS vendored property names</string>
+ <key>scope</key>
+ <string>support.type.vendored.property-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Less/SCSS right-hand variables (@/$-prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-value variable, source.css meta.property-value variable.other.less, source.css meta.property-value variable.other.less punctuation.definition.variable.less, meta.definition.variable.scss</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS variables (--prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-list variable, meta.property-list variable.other.less, meta.property-list variable.other.less punctuation.definition.variable.less</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Percentage values, styled the same as numbers</string>
+ <key>scope</key>
+ <string>keyword.other.unit.percentage.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Attribute selectors, styled the same as strings</string>
+ <key>scope</key>
+ <string>source.css meta.attribute-selector</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML keys, other left-hand assignments</string>
+ <key>scope</key>
+ <string>keyword.other.definition.ini, punctuation.support.type.property-name.json, support.type.property-name.json, punctuation.support.type.property-name.toml, support.type.property-name.toml, entity.name.tag.yaml, punctuation.support.type.property-name.yaml, support.type.property-name.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML constants</string>
+ <key>scope</key>
+ <string>constant.language.json, constant.language.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchors</string>
+ <key>scope</key>
+ <string>entity.name.type.anchor.yaml, variable.other.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML tables / ini groups</string>
+ <key>scope</key>
+ <string>support.type.property-name.table, entity.name.section.group-title.ini</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML dates</string>
+ <key>scope</key>
+ <string>constant.other.time.datetime.offset.toml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchor puctuation</string>
+ <key>scope</key>
+ <string>punctuation.definition.anchor.yaml, punctuation.definition.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML triple dashes</string>
+ <key>scope</key>
+ <string>entity.other.document.begin.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markup Diff</string>
+ <key>scope</key>
+ <string>markup.changed.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff</string>
+ <key>scope</key>
+ <string>meta.diff.header.from-file, meta.diff.header.to-file, punctuation.definition.from-file.diff, punctuation.definition.to-file.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Inserted</string>
+ <key>scope</key>
+ <string>markup.inserted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Deleted</string>
+ <key>scope</key>
+ <string>markup.deleted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv left-hand side assignments</string>
+ <key>scope</key>
+ <string>variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv reference to existing env variable</string>
+ <key>scope</key>
+ <string>string.quoted variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript functions</string>
+ <key>scope</key>
+ <string>support.function.builtin.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript constants</string>
+ <key>scope</key>
+ <string>constant.language.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comment keywords</string>
+ <key>scope</key>
+ <string>comment meta.annotation.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>go:embed, go:build, etc.</string>
+ <key>scope</key>
+ <string>comment meta.annotation.parameters.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Go constants (nil, true, false)</string>
+ <key>scope</key>
+ <string>constant.language.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL variables</string>
+ <key>scope</key>
+ <string>variable.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL aliases</string>
+ <key>scope</key>
+ <string>string.unquoted.alias.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#dd7878</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL enum members</string>
+ <key>scope</key>
+ <string>constant.character.enum.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL field in types</string>
+ <key>scope</key>
+ <string>meta.objectvalues.graphql constant.object.key.graphql string.unquoted.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#dd7878</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML DOCTYPE as keyword</string>
+ <key>scope</key>
+ <string>keyword.other.doctype, meta.tag.sgml.doctype punctuation.definition.tag, meta.tag.metadata.doctype entity.name.tag, meta.tag.metadata.doctype punctuation.definition.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML-like &lt;tags/&gt;</string>
+ <key>scope</key>
+ <string>entity.name.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Special characters like &amp;amp;</string>
+ <key>scope</key>
+ <string>text.html constant.character.entity, text.html constant.character.entity punctuation, constant.character.entity.xml, constant.character.entity.xml punctuation, constant.character.entity.js.jsx, constant.charactger.entity.js.jsx punctuation, constant.character.entity.tsx, constant.character.entity.tsx punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML tag attribute values</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Components</string>
+ <key>scope</key>
+ <string>support.class.component, support.class.component.jsx, support.class.component.tsx, support.class.component.vue</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Annotations</string>
+ <key>scope</key>
+ <string>punctuation.definition.annotation, storage.type.annotation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java enums</string>
+ <key>scope</key>
+ <string>constant.other.enum.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java imports</string>
+ <key>scope</key>
+ <string>storage.modifier.import.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Javadoc</string>
+ <key>scope</key>
+ <string>comment.block.javadoc.java keyword.other.documentation.javadoc.java</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Exported Variable</string>
+ <key>scope</key>
+ <string>meta.export variable.other.readwrite.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JS/TS constants &amp; properties</string>
+ <key>scope</key>
+ <string>variable.other.constant.js, variable.other.constant.ts, variable.other.property.js, variable.other.property.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc; these are mainly params, so styled as such</string>
+ <key>scope</key>
+ <string>variable.other.jsdoc, comment.block.documentation variable.other</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.jsdoc</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>support.type.object.console.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Node constants as keywords (module, etc.)</string>
+ <key>scope</key>
+ <string>support.constant.node, support.type.object.module.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>implements as keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.implements</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Builtin types</string>
+ <key>scope</key>
+ <string>constant.language.null.js, constant.language.null.ts, constant.language.undefined.js, constant.language.undefined.ts, support.type.builtin.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter.generic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arrow functions</string>
+ <key>scope</key>
+ <string>keyword.declaration.function.arrow.js, storage.type.function.arrow.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorator punctuations (decorators inherit from blue functions, instead of styleguide peach)</string>
+ <key>scope</key>
+ <string>punctuation.decorator.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Extra JS/TS keywords</string>
+ <key>scope</key>
+ <string>keyword.operator.expression.in.js, keyword.operator.expression.in.ts, keyword.operator.expression.infer.ts, keyword.operator.expression.instanceof.js, keyword.operator.expression.instanceof.ts, keyword.operator.expression.is, keyword.operator.expression.keyof.ts, keyword.operator.expression.of.js, keyword.operator.expression.of.ts, keyword.operator.expression.typeof.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia macros</string>
+ <key>scope</key>
+ <string>support.function.macro.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia language constants (true, false)</string>
+ <key>scope</key>
+ <string>constant.language.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia other constants (these seem to be arguments inside arrays)</string>
+ <key>scope</key>
+ <string>constant.other.symbol.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX preamble</string>
+ <key>scope</key>
+ <string>text.tex keyword.control.preamble</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX be functions</string>
+ <key>scope</key>
+ <string>text.tex support.function.be</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX math</string>
+ <key>scope</key>
+ <string>constant.other.general.math.tex</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#dd7878</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring keywords</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua storage.type.annotation.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring variables</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua entity.name.variable.lua, comment.line.double-dash.documentation.lua variable.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.1.markdown punctuation.definition.heading.markdown, heading.1.markdown, markup.heading.atx.1.mdx, markup.heading.atx.1.mdx punctuation.definition.heading.mdx, markup.heading.setext.1.markdown, markup.heading.heading-0.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.2.markdown punctuation.definition.heading.markdown, heading.2.markdown, markup.heading.atx.2.mdx, markup.heading.atx.2.mdx punctuation.definition.heading.mdx, markup.heading.setext.2.markdown, markup.heading.heading-1.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.3.markdown punctuation.definition.heading.markdown, heading.3.markdown, markup.heading.atx.3.mdx, markup.heading.atx.3.mdx punctuation.definition.heading.mdx, markup.heading.heading-2.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.4.markdown punctuation.definition.heading.markdown, heading.4.markdown, markup.heading.atx.4.mdx, markup.heading.atx.4.mdx punctuation.definition.heading.mdx, markup.heading.heading-3.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.5.markdown punctuation.definition.heading.markdown, heading.5.markdown, markup.heading.atx.5.mdx, markup.heading.atx.5.mdx punctuation.definition.heading.mdx, markup.heading.heading-4.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.6.markdown punctuation.definition.heading.markdown, heading.6.markdown, markup.heading.atx.6.mdx, markup.heading.atx.6.mdx punctuation.definition.heading.mdx, markup.heading.heading-5.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.bold</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.italic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.strikethrough</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#6c6f85</string>
+ <key>fontStyle</key>
+ <string>strikethrough</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown auto links</string>
+ <key>scope</key>
+ <string>punctuation.definition.link, markup.underline.link</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown links</string>
+ <key>scope</key>
+ <string>text.html.markdown punctuation.definition.link.title, string.other.link.title.markdown, markup.link, punctuation.definition.constant.markdown, constant.other.reference.link.markdown, markup.substitution.attribute-reference</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#7287fd</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown code spans</string>
+ <key>scope</key>
+ <string>punctuation.definition.raw.markdown, markup.inline.raw.string.markdown, markup.raw.block.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backtick language identifier</string>
+ <key>scope</key>
+ <string>fenced_code.block.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backticks</string>
+ <key>scope</key>
+ <string>markup.fenced_code.block punctuation.definition, markup.raw support.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#7c7f93</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown quotes</string>
+ <key>scope</key>
+ <string>markup.quote, punctuation.definition.quote.begin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown separators</string>
+ <key>scope</key>
+ <string>meta.separator.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown list bullets</string>
+ <key>scope</key>
+ <string>punctuation.definition.list.begin.markdown, markup.list.bullet</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix attribute names</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name.multipart.nix, entity.other.attribute-name.single.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix parameter names</string>
+ <key>scope</key>
+ <string>variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix interpolated parameter names</string>
+ <key>scope</key>
+ <string>meta.embedded variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#7287fd</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix paths</string>
+ <key>scope</key>
+ <string>string.unquoted.path.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Attributes</string>
+ <key>scope</key>
+ <string>support.attribute.builtin, meta.attribute.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Parameters (needed for the leading dollar sign)</string>
+ <key>scope</key>
+ <string>meta.function.parameters.php punctuation.definition.variable.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Constants (null, __FILE__, etc.)</string>
+ <key>scope</key>
+ <string>constant.language.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP functions</string>
+ <key>scope</key>
+ <string>text.html.php support.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHPdoc keywords</string>
+ <key>scope</key>
+ <string>keyword.other.phpdoc.php</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python argument functions reset to text, otherwise they inherit blue from function-call</string>
+ <key>scope</key>
+ <string>support.variable.magic.python, meta.function-call.arguments.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python double underscore functions</string>
+ <key>scope</key>
+ <string>support.function.magic.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python `self` keyword</string>
+ <key>scope</key>
+ <string>variable.parameter.function.language.special.self.python, variable.language.special.self.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python keyword flow/logical (for ... in)</string>
+ <key>scope</key>
+ <string>keyword.control.flow.python, keyword.operator.logical.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python storage type</string>
+ <key>scope</key>
+ <string>storage.type.function.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function support</string>
+ <key>scope</key>
+ <string>support.token.decorator.python, meta.function.decorator.identifier.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function calls</string>
+ <key>scope</key>
+ <string>meta.function-call.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function decorators</string>
+ <key>scope</key>
+ <string>entity.name.function.decorator.python, punctuation.definition.decorator.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python placeholder reset to normal string</string>
+ <key>scope</key>
+ <string>constant.character.format.placeholder.other.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python exception &amp; builtins such as exit()</string>
+ <key>scope</key>
+ <string>support.type.exception.python, support.function.builtin.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>entity.name.type</string>
+ <key>scope</key>
+ <string>support.type.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python constants (True/False)</string>
+ <key>scope</key>
+ <string>constant.language.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arguments accessed later in the function body</string>
+ <key>scope</key>
+ <string>meta.indexed-name.python, meta.item-access.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python f-strings/binary/unicode storage types</string>
+ <key>scope</key>
+ <string>storage.type.string.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python type hints</string>
+ <key>scope</key>
+ <string>meta.function.parameters.python</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex string begin/end in JS/TS</string>
+ <key>scope</key>
+ <string>string.regexp punctuation.definition.string.begin, string.regexp punctuation.definition.string.end</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex anchors (^, $)</string>
+ <key>scope</key>
+ <string>keyword.control.anchor.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex regular string match</string>
+ <key>scope</key>
+ <string>string.regexp.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex group parenthesis &amp; backreference (\1, \2, \3, ...)</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.regexp, keyword.other.back-reference.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#40a02b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character class []</string>
+ <key>scope</key>
+ <string>punctuation.definition.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character classes (\d, \w, \s)</string>
+ <key>scope</key>
+ <string>constant.other.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex range</string>
+ <key>scope</key>
+ <string>constant.other.character-class.range.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#dc8a78</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex quantifier</string>
+ <key>scope</key>
+ <string>keyword.operator.quantifier.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex constant/numeric</string>
+ <key>scope</key>
+ <string>constant.character.numeric.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex lookaheads, negative lookaheads, lookbehinds, negative lookbehinds</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.no-capture.regexp, meta.assertion.look-ahead.regexp, meta.assertion.negative-look-ahead.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute</string>
+ <key>scope</key>
+ <string>meta.annotation.rust, meta.annotation.rust punctuation, meta.attribute.rust, punctuation.definition.attribute.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute strings</string>
+ <key>scope</key>
+ <string>meta.attribute.rust string.quoted.double.rust, meta.attribute.rust string.quoted.single.char.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust keyword</string>
+ <key>scope</key>
+ <string>entity.name.function.macro.rules.rust, storage.type.module.rust, storage.modifier.rust, storage.type.struct.rust, storage.type.enum.rust, storage.type.trait.rust, storage.type.union.rust, storage.type.impl.rust, storage.type.rust, storage.type.function.rust, storage.type.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust u/i32, u/i64, etc.</string>
+ <key>scope</key>
+ <string>entity.name.type.numeric.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust generic</string>
+ <key>scope</key>
+ <string>meta.generic.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust impl</string>
+ <key>scope</key>
+ <string>entity.name.impl.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust module</string>
+ <key>scope</key>
+ <string>entity.name.module.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust trait</string>
+ <key>scope</key>
+ <string>entity.name.trait.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust struct</string>
+ <key>scope</key>
+ <string>storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust union</string>
+ <key>scope</key>
+ <string>entity.name.union.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust enum member</string>
+ <key>scope</key>
+ <string>meta.enum.rust storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust macro</string>
+ <key>scope</key>
+ <string>support.macro.rust, meta.macro.rust support.function.rust, entity.name.function.macro.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust lifetime</string>
+ <key>scope</key>
+ <string>storage.modifier.lifetime.rust, entity.name.type.lifetime</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust string formatting</string>
+ <key>scope</key>
+ <string>string.quoted.double.rust constant.other.placeholder.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust return type generic</string>
+ <key>scope</key>
+ <string>meta.function.return-type.rust meta.generic.rust storage.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust functions</string>
+ <key>scope</key>
+ <string>meta.function.call.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust angle brackets</string>
+ <key>scope</key>
+ <string>punctuation.brackets.angle.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#04a5e5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust constants</string>
+ <key>scope</key>
+ <string>constant.other.caps.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust function parameters</string>
+ <key>scope</key>
+ <string>meta.function.definition.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#e64553</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust closure variables</string>
+ <key>scope</key>
+ <string>meta.function.call.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust self</string>
+ <key>scope</key>
+ <string>variable.language.self.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust metavariable names</string>
+ <key>scope</key>
+ <string>variable.other.metavariable.name.rust, meta.macro.metavariable.rust keyword.operator.macro.dollar.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang</string>
+ <key>scope</key>
+ <string>comment.line.shebang, comment.line.shebang punctuation.definition.comment, comment.line.shebang, punctuation.definition.comment.shebang.shell, meta.shebang.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang command</string>
+ <key>scope</key>
+ <string>comment.line.shebang constant.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command</string>
+ <key>scope</key>
+ <string>meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation, meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command variable</string>
+ <key>scope</key>
+ <string>meta.string meta.interpolation.parameter.shell variable.other.readwrite</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.shell punctuation.section.interpolation, punctuation.definition.evaluation.backticks.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell EOF</string>
+ <key>scope</key>
+ <string>entity.name.tag.heredoc.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell quoted variable</string>
+ <key>scope</key>
+ <string>string.quoted.double.shell variable.other.normal.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#4c4f69</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.synopsis.man, markup.heading.title.man, markup.heading.other.man, markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8839ef</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.commands.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#1e66f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ea76cb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Man page options</string>
+ <key>scope</key>
+ <string>entity.name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#179299</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.1.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#d20f39</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.2.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fe640b</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#df8e1d</string>
+ </dict>
+ </dict>
+ </array>
+ </dict>
+</plist> \ No newline at end of file
diff --git a/ar/.config/bat/themes/Catppuccin Macchiato.tmTheme b/ar/.config/bat/themes/Catppuccin Macchiato.tmTheme
new file mode 100644
index 0000000..82baa1a
--- /dev/null
+++ b/ar/.config/bat/themes/Catppuccin Macchiato.tmTheme
@@ -0,0 +1,2059 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>name</key>
+ <string>Catppuccin Macchiato</string>
+ <key>semanticClass</key>
+ <string>theme.dark.catppuccin-macchiato</string>
+ <key>uuid</key>
+ <string>02b2bdf3-9eb7-4396-bf04-f17f1468f99f</string>
+ <key>author</key>
+ <string>Catppuccin Org</string>
+ <key>colorSpaceName</key>
+ <string>sRGB</string>
+ <key>settings</key>
+ <array>
+ <dict>
+ <key>settings</key>
+ <dict>
+ <key>background</key>
+ <string>#24273a</string>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ <key>caret</key>
+ <string>#f4dbd6</string>
+ <key>lineHighlight</key>
+ <string>#363a4f</string>
+ <key>misspelling</key>
+ <string>#ed8796</string>
+ <key>accent</key>
+ <string>#c6a0f6</string>
+ <key>selection</key>
+ <string>#939ab740</string>
+ <key>activeGuide</key>
+ <string>#494d64</string>
+ <key>findHighlight</key>
+ <string>#455c6d</string>
+ <key>gutterForeground</key>
+ <string>#8087a2</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Basic text &amp; variable names (incl. leading punctuation)</string>
+ <key>scope</key>
+ <string>text, source, variable.other.readwrite, punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Parentheses, Brackets, Braces</string>
+ <key>scope</key>
+ <string>punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#939ab7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comments</string>
+ <key>scope</key>
+ <string>comment, punctuation.definition.comment</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#6e738d</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>string, punctuation.definition.string</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>constant.character.escape</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Booleans, constants, numbers</string>
+ <key>scope</key>
+ <string>constant.numeric, variable.other.constant, entity.name.constant, constant.language.boolean, constant.language.false, constant.language.true, keyword.other.unit.user-defined, keyword.other.unit.suffix.floating-point</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>keyword, keyword.operator.word, keyword.operator.new, variable.language.super, support.type.primitive, storage.type, storage.modifier, punctuation.definition.keyword</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.tag.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Punctuation</string>
+ <key>scope</key>
+ <string>keyword.operator, punctuation.accessor, punctuation.definition.generic, meta.function.closure punctuation.section.parameters, punctuation.definition.tag, punctuation.separator.key-value</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.function, meta.function-call.method, support.function, support.function.misc, variable.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes</string>
+ <key>scope</key>
+ <string>entity.name.class, entity.other.inherited-class, support.class, meta.function-call.constructor, entity.name.struct</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum</string>
+ <key>scope</key>
+ <string>entity.name.enum</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum member</string>
+ <key>scope</key>
+ <string>meta.enum variable.other.readwrite, variable.other.enummember</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>meta.property.object</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Types</string>
+ <key>scope</key>
+ <string>meta.type, meta.type-alias, support.type, entity.name.type</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorators</string>
+ <key>scope</key>
+ <string>meta.annotation variable.function, meta.annotation variable.annotation.function, meta.annotation punctuation.definition.annotation, meta.decorator, punctuation.decorator</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter, meta.function.parameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Built-ins</string>
+ <key>scope</key>
+ <string>constant.language, support.function.builtin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.other.attribute-name.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Preprocessor directives</string>
+ <key>scope</key>
+ <string>keyword.control.directive, punctuation.definition.directive</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Type parameters</string>
+ <key>scope</key>
+ <string>punctuation.definition.typeparameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Namespaces</string>
+ <key>scope</key>
+ <string>entity.name.namespace</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Property names (left hand assignments in json/yaml/css)</string>
+ <key>scope</key>
+ <string>support.type.property-name.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>This/Self keyword</string>
+ <key>scope</key>
+ <string>variable.language.this, variable.language.this punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>variable.object.property</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>String template interpolation</string>
+ <key>scope</key>
+ <string>string.template variable, string variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>`new` as bold</string>
+ <key>scope</key>
+ <string>keyword.operator.new</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ extern keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.specifier.extern.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ scope resolution</string>
+ <key>scope</key>
+ <string>entity.name.scope-resolution.template.call.cpp, entity.name.scope-resolution.parameter.cpp, entity.name.scope-resolution.cpp, entity.name.scope-resolution.function.definition.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ doc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.doxygen</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ operators</string>
+ <key>scope</key>
+ <string>storage.modifier.reference.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# Interpolated Strings</string>
+ <key>scope</key>
+ <string>meta.interpolation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# xml-style docs</string>
+ <key>scope</key>
+ <string>comment.block.documentation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes, reflecting the className color in JSX</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.class.css, entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Operators</string>
+ <key>scope</key>
+ <string>punctuation.separator.operator.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Pseudo classes</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.pseudo-class</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css constant.other.unicode-range</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css variable.parameter.url</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS vendored property names</string>
+ <key>scope</key>
+ <string>support.type.vendored.property-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Less/SCSS right-hand variables (@/$-prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-value variable, source.css meta.property-value variable.other.less, source.css meta.property-value variable.other.less punctuation.definition.variable.less, meta.definition.variable.scss</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS variables (--prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-list variable, meta.property-list variable.other.less, meta.property-list variable.other.less punctuation.definition.variable.less</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Percentage values, styled the same as numbers</string>
+ <key>scope</key>
+ <string>keyword.other.unit.percentage.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Attribute selectors, styled the same as strings</string>
+ <key>scope</key>
+ <string>source.css meta.attribute-selector</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML keys, other left-hand assignments</string>
+ <key>scope</key>
+ <string>keyword.other.definition.ini, punctuation.support.type.property-name.json, support.type.property-name.json, punctuation.support.type.property-name.toml, support.type.property-name.toml, entity.name.tag.yaml, punctuation.support.type.property-name.yaml, support.type.property-name.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML constants</string>
+ <key>scope</key>
+ <string>constant.language.json, constant.language.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchors</string>
+ <key>scope</key>
+ <string>entity.name.type.anchor.yaml, variable.other.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML tables / ini groups</string>
+ <key>scope</key>
+ <string>support.type.property-name.table, entity.name.section.group-title.ini</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML dates</string>
+ <key>scope</key>
+ <string>constant.other.time.datetime.offset.toml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchor puctuation</string>
+ <key>scope</key>
+ <string>punctuation.definition.anchor.yaml, punctuation.definition.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML triple dashes</string>
+ <key>scope</key>
+ <string>entity.other.document.begin.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markup Diff</string>
+ <key>scope</key>
+ <string>markup.changed.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff</string>
+ <key>scope</key>
+ <string>meta.diff.header.from-file, meta.diff.header.to-file, punctuation.definition.from-file.diff, punctuation.definition.to-file.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Inserted</string>
+ <key>scope</key>
+ <string>markup.inserted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Deleted</string>
+ <key>scope</key>
+ <string>markup.deleted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv left-hand side assignments</string>
+ <key>scope</key>
+ <string>variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv reference to existing env variable</string>
+ <key>scope</key>
+ <string>string.quoted variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript functions</string>
+ <key>scope</key>
+ <string>support.function.builtin.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript constants</string>
+ <key>scope</key>
+ <string>constant.language.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comment keywords</string>
+ <key>scope</key>
+ <string>comment meta.annotation.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>go:embed, go:build, etc.</string>
+ <key>scope</key>
+ <string>comment meta.annotation.parameters.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Go constants (nil, true, false)</string>
+ <key>scope</key>
+ <string>constant.language.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL variables</string>
+ <key>scope</key>
+ <string>variable.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL aliases</string>
+ <key>scope</key>
+ <string>string.unquoted.alias.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f0c6c6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL enum members</string>
+ <key>scope</key>
+ <string>constant.character.enum.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL field in types</string>
+ <key>scope</key>
+ <string>meta.objectvalues.graphql constant.object.key.graphql string.unquoted.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f0c6c6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML DOCTYPE as keyword</string>
+ <key>scope</key>
+ <string>keyword.other.doctype, meta.tag.sgml.doctype punctuation.definition.tag, meta.tag.metadata.doctype entity.name.tag, meta.tag.metadata.doctype punctuation.definition.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML-like &lt;tags/&gt;</string>
+ <key>scope</key>
+ <string>entity.name.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Special characters like &amp;amp;</string>
+ <key>scope</key>
+ <string>text.html constant.character.entity, text.html constant.character.entity punctuation, constant.character.entity.xml, constant.character.entity.xml punctuation, constant.character.entity.js.jsx, constant.charactger.entity.js.jsx punctuation, constant.character.entity.tsx, constant.character.entity.tsx punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML tag attribute values</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Components</string>
+ <key>scope</key>
+ <string>support.class.component, support.class.component.jsx, support.class.component.tsx, support.class.component.vue</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Annotations</string>
+ <key>scope</key>
+ <string>punctuation.definition.annotation, storage.type.annotation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java enums</string>
+ <key>scope</key>
+ <string>constant.other.enum.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java imports</string>
+ <key>scope</key>
+ <string>storage.modifier.import.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Javadoc</string>
+ <key>scope</key>
+ <string>comment.block.javadoc.java keyword.other.documentation.javadoc.java</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Exported Variable</string>
+ <key>scope</key>
+ <string>meta.export variable.other.readwrite.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JS/TS constants &amp; properties</string>
+ <key>scope</key>
+ <string>variable.other.constant.js, variable.other.constant.ts, variable.other.property.js, variable.other.property.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc; these are mainly params, so styled as such</string>
+ <key>scope</key>
+ <string>variable.other.jsdoc, comment.block.documentation variable.other</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.jsdoc</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>support.type.object.console.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Node constants as keywords (module, etc.)</string>
+ <key>scope</key>
+ <string>support.constant.node, support.type.object.module.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>implements as keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.implements</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Builtin types</string>
+ <key>scope</key>
+ <string>constant.language.null.js, constant.language.null.ts, constant.language.undefined.js, constant.language.undefined.ts, support.type.builtin.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter.generic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arrow functions</string>
+ <key>scope</key>
+ <string>keyword.declaration.function.arrow.js, storage.type.function.arrow.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorator punctuations (decorators inherit from blue functions, instead of styleguide peach)</string>
+ <key>scope</key>
+ <string>punctuation.decorator.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Extra JS/TS keywords</string>
+ <key>scope</key>
+ <string>keyword.operator.expression.in.js, keyword.operator.expression.in.ts, keyword.operator.expression.infer.ts, keyword.operator.expression.instanceof.js, keyword.operator.expression.instanceof.ts, keyword.operator.expression.is, keyword.operator.expression.keyof.ts, keyword.operator.expression.of.js, keyword.operator.expression.of.ts, keyword.operator.expression.typeof.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia macros</string>
+ <key>scope</key>
+ <string>support.function.macro.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia language constants (true, false)</string>
+ <key>scope</key>
+ <string>constant.language.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia other constants (these seem to be arguments inside arrays)</string>
+ <key>scope</key>
+ <string>constant.other.symbol.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX preamble</string>
+ <key>scope</key>
+ <string>text.tex keyword.control.preamble</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX be functions</string>
+ <key>scope</key>
+ <string>text.tex support.function.be</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX math</string>
+ <key>scope</key>
+ <string>constant.other.general.math.tex</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f0c6c6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring keywords</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua storage.type.annotation.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring variables</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua entity.name.variable.lua, comment.line.double-dash.documentation.lua variable.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.1.markdown punctuation.definition.heading.markdown, heading.1.markdown, markup.heading.atx.1.mdx, markup.heading.atx.1.mdx punctuation.definition.heading.mdx, markup.heading.setext.1.markdown, markup.heading.heading-0.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.2.markdown punctuation.definition.heading.markdown, heading.2.markdown, markup.heading.atx.2.mdx, markup.heading.atx.2.mdx punctuation.definition.heading.mdx, markup.heading.setext.2.markdown, markup.heading.heading-1.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.3.markdown punctuation.definition.heading.markdown, heading.3.markdown, markup.heading.atx.3.mdx, markup.heading.atx.3.mdx punctuation.definition.heading.mdx, markup.heading.heading-2.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.4.markdown punctuation.definition.heading.markdown, heading.4.markdown, markup.heading.atx.4.mdx, markup.heading.atx.4.mdx punctuation.definition.heading.mdx, markup.heading.heading-3.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.5.markdown punctuation.definition.heading.markdown, heading.5.markdown, markup.heading.atx.5.mdx, markup.heading.atx.5.mdx punctuation.definition.heading.mdx, markup.heading.heading-4.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.6.markdown punctuation.definition.heading.markdown, heading.6.markdown, markup.heading.atx.6.mdx, markup.heading.atx.6.mdx punctuation.definition.heading.mdx, markup.heading.heading-5.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.bold</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.italic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.strikethrough</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a5adcb</string>
+ <key>fontStyle</key>
+ <string>strikethrough</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown auto links</string>
+ <key>scope</key>
+ <string>punctuation.definition.link, markup.underline.link</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown links</string>
+ <key>scope</key>
+ <string>text.html.markdown punctuation.definition.link.title, string.other.link.title.markdown, markup.link, punctuation.definition.constant.markdown, constant.other.reference.link.markdown, markup.substitution.attribute-reference</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#b7bdf8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown code spans</string>
+ <key>scope</key>
+ <string>punctuation.definition.raw.markdown, markup.inline.raw.string.markdown, markup.raw.block.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backtick language identifier</string>
+ <key>scope</key>
+ <string>fenced_code.block.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backticks</string>
+ <key>scope</key>
+ <string>markup.fenced_code.block punctuation.definition, markup.raw support.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#939ab7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown quotes</string>
+ <key>scope</key>
+ <string>markup.quote, punctuation.definition.quote.begin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown separators</string>
+ <key>scope</key>
+ <string>meta.separator.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown list bullets</string>
+ <key>scope</key>
+ <string>punctuation.definition.list.begin.markdown, markup.list.bullet</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix attribute names</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name.multipart.nix, entity.other.attribute-name.single.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix parameter names</string>
+ <key>scope</key>
+ <string>variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix interpolated parameter names</string>
+ <key>scope</key>
+ <string>meta.embedded variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#b7bdf8</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix paths</string>
+ <key>scope</key>
+ <string>string.unquoted.path.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Attributes</string>
+ <key>scope</key>
+ <string>support.attribute.builtin, meta.attribute.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Parameters (needed for the leading dollar sign)</string>
+ <key>scope</key>
+ <string>meta.function.parameters.php punctuation.definition.variable.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Constants (null, __FILE__, etc.)</string>
+ <key>scope</key>
+ <string>constant.language.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP functions</string>
+ <key>scope</key>
+ <string>text.html.php support.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHPdoc keywords</string>
+ <key>scope</key>
+ <string>keyword.other.phpdoc.php</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python argument functions reset to text, otherwise they inherit blue from function-call</string>
+ <key>scope</key>
+ <string>support.variable.magic.python, meta.function-call.arguments.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python double underscore functions</string>
+ <key>scope</key>
+ <string>support.function.magic.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python `self` keyword</string>
+ <key>scope</key>
+ <string>variable.parameter.function.language.special.self.python, variable.language.special.self.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python keyword flow/logical (for ... in)</string>
+ <key>scope</key>
+ <string>keyword.control.flow.python, keyword.operator.logical.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python storage type</string>
+ <key>scope</key>
+ <string>storage.type.function.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function support</string>
+ <key>scope</key>
+ <string>support.token.decorator.python, meta.function.decorator.identifier.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function calls</string>
+ <key>scope</key>
+ <string>meta.function-call.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function decorators</string>
+ <key>scope</key>
+ <string>entity.name.function.decorator.python, punctuation.definition.decorator.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python placeholder reset to normal string</string>
+ <key>scope</key>
+ <string>constant.character.format.placeholder.other.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python exception &amp; builtins such as exit()</string>
+ <key>scope</key>
+ <string>support.type.exception.python, support.function.builtin.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>entity.name.type</string>
+ <key>scope</key>
+ <string>support.type.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python constants (True/False)</string>
+ <key>scope</key>
+ <string>constant.language.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arguments accessed later in the function body</string>
+ <key>scope</key>
+ <string>meta.indexed-name.python, meta.item-access.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python f-strings/binary/unicode storage types</string>
+ <key>scope</key>
+ <string>storage.type.string.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python type hints</string>
+ <key>scope</key>
+ <string>meta.function.parameters.python</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex string begin/end in JS/TS</string>
+ <key>scope</key>
+ <string>string.regexp punctuation.definition.string.begin, string.regexp punctuation.definition.string.end</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex anchors (^, $)</string>
+ <key>scope</key>
+ <string>keyword.control.anchor.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex regular string match</string>
+ <key>scope</key>
+ <string>string.regexp.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex group parenthesis &amp; backreference (\1, \2, \3, ...)</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.regexp, keyword.other.back-reference.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6da95</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character class []</string>
+ <key>scope</key>
+ <string>punctuation.definition.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character classes (\d, \w, \s)</string>
+ <key>scope</key>
+ <string>constant.other.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex range</string>
+ <key>scope</key>
+ <string>constant.other.character-class.range.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f4dbd6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex quantifier</string>
+ <key>scope</key>
+ <string>keyword.operator.quantifier.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex constant/numeric</string>
+ <key>scope</key>
+ <string>constant.character.numeric.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex lookaheads, negative lookaheads, lookbehinds, negative lookbehinds</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.no-capture.regexp, meta.assertion.look-ahead.regexp, meta.assertion.negative-look-ahead.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute</string>
+ <key>scope</key>
+ <string>meta.annotation.rust, meta.annotation.rust punctuation, meta.attribute.rust, punctuation.definition.attribute.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute strings</string>
+ <key>scope</key>
+ <string>meta.attribute.rust string.quoted.double.rust, meta.attribute.rust string.quoted.single.char.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust keyword</string>
+ <key>scope</key>
+ <string>entity.name.function.macro.rules.rust, storage.type.module.rust, storage.modifier.rust, storage.type.struct.rust, storage.type.enum.rust, storage.type.trait.rust, storage.type.union.rust, storage.type.impl.rust, storage.type.rust, storage.type.function.rust, storage.type.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust u/i32, u/i64, etc.</string>
+ <key>scope</key>
+ <string>entity.name.type.numeric.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust generic</string>
+ <key>scope</key>
+ <string>meta.generic.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust impl</string>
+ <key>scope</key>
+ <string>entity.name.impl.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust module</string>
+ <key>scope</key>
+ <string>entity.name.module.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust trait</string>
+ <key>scope</key>
+ <string>entity.name.trait.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust struct</string>
+ <key>scope</key>
+ <string>storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust union</string>
+ <key>scope</key>
+ <string>entity.name.union.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust enum member</string>
+ <key>scope</key>
+ <string>meta.enum.rust storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust macro</string>
+ <key>scope</key>
+ <string>support.macro.rust, meta.macro.rust support.function.rust, entity.name.function.macro.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust lifetime</string>
+ <key>scope</key>
+ <string>storage.modifier.lifetime.rust, entity.name.type.lifetime</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust string formatting</string>
+ <key>scope</key>
+ <string>string.quoted.double.rust constant.other.placeholder.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust return type generic</string>
+ <key>scope</key>
+ <string>meta.function.return-type.rust meta.generic.rust storage.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust functions</string>
+ <key>scope</key>
+ <string>meta.function.call.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust angle brackets</string>
+ <key>scope</key>
+ <string>punctuation.brackets.angle.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#91d7e3</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust constants</string>
+ <key>scope</key>
+ <string>constant.other.caps.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust function parameters</string>
+ <key>scope</key>
+ <string>meta.function.definition.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ee99a0</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust closure variables</string>
+ <key>scope</key>
+ <string>meta.function.call.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust self</string>
+ <key>scope</key>
+ <string>variable.language.self.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust metavariable names</string>
+ <key>scope</key>
+ <string>variable.other.metavariable.name.rust, meta.macro.metavariable.rust keyword.operator.macro.dollar.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang</string>
+ <key>scope</key>
+ <string>comment.line.shebang, comment.line.shebang punctuation.definition.comment, comment.line.shebang, punctuation.definition.comment.shebang.shell, meta.shebang.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang command</string>
+ <key>scope</key>
+ <string>comment.line.shebang constant.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command</string>
+ <key>scope</key>
+ <string>meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation, meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command variable</string>
+ <key>scope</key>
+ <string>meta.string meta.interpolation.parameter.shell variable.other.readwrite</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.shell punctuation.section.interpolation, punctuation.definition.evaluation.backticks.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell EOF</string>
+ <key>scope</key>
+ <string>entity.name.tag.heredoc.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell quoted variable</string>
+ <key>scope</key>
+ <string>string.quoted.double.shell variable.other.normal.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cad3f5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.synopsis.man, markup.heading.title.man, markup.heading.other.man, markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#c6a0f6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.commands.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8aadf4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5bde6</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Man page options</string>
+ <key>scope</key>
+ <string>entity.name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#8bd5ca</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.1.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#ed8796</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.2.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5a97f</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eed49f</string>
+ </dict>
+ </dict>
+ </array>
+ </dict>
+</plist> \ No newline at end of file
diff --git a/ar/.config/bat/themes/Catppuccin Mocha.tmTheme b/ar/.config/bat/themes/Catppuccin Mocha.tmTheme
new file mode 100644
index 0000000..cd3a27b
--- /dev/null
+++ b/ar/.config/bat/themes/Catppuccin Mocha.tmTheme
@@ -0,0 +1,2059 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>name</key>
+ <string>Catppuccin Mocha</string>
+ <key>semanticClass</key>
+ <string>theme.dark.catppuccin-mocha</string>
+ <key>uuid</key>
+ <string>627ce890-fabb-4d39-9819-7be71f4bdca7</string>
+ <key>author</key>
+ <string>Catppuccin Org</string>
+ <key>colorSpaceName</key>
+ <string>sRGB</string>
+ <key>settings</key>
+ <array>
+ <dict>
+ <key>settings</key>
+ <dict>
+ <key>background</key>
+ <string>#1e1e2e</string>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ <key>caret</key>
+ <string>#f5e0dc</string>
+ <key>lineHighlight</key>
+ <string>#313244</string>
+ <key>misspelling</key>
+ <string>#f38ba8</string>
+ <key>accent</key>
+ <string>#cba6f7</string>
+ <key>selection</key>
+ <string>#9399b240</string>
+ <key>activeGuide</key>
+ <string>#45475a</string>
+ <key>findHighlight</key>
+ <string>#3e5767</string>
+ <key>gutterForeground</key>
+ <string>#7f849c</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Basic text &amp; variable names (incl. leading punctuation)</string>
+ <key>scope</key>
+ <string>text, source, variable.other.readwrite, punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Parentheses, Brackets, Braces</string>
+ <key>scope</key>
+ <string>punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#9399b2</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comments</string>
+ <key>scope</key>
+ <string>comment, punctuation.definition.comment</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#6c7086</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>string, punctuation.definition.string</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>constant.character.escape</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Booleans, constants, numbers</string>
+ <key>scope</key>
+ <string>constant.numeric, variable.other.constant, entity.name.constant, constant.language.boolean, constant.language.false, constant.language.true, keyword.other.unit.user-defined, keyword.other.unit.suffix.floating-point</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>keyword, keyword.operator.word, keyword.operator.new, variable.language.super, support.type.primitive, storage.type, storage.modifier, punctuation.definition.keyword</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.tag.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Punctuation</string>
+ <key>scope</key>
+ <string>keyword.operator, punctuation.accessor, punctuation.definition.generic, meta.function.closure punctuation.section.parameters, punctuation.definition.tag, punctuation.separator.key-value</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.name.function, meta.function-call.method, support.function, support.function.misc, variable.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes</string>
+ <key>scope</key>
+ <string>entity.name.class, entity.other.inherited-class, support.class, meta.function-call.constructor, entity.name.struct</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum</string>
+ <key>scope</key>
+ <string>entity.name.enum</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Enum member</string>
+ <key>scope</key>
+ <string>meta.enum variable.other.readwrite, variable.other.enummember</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>meta.property.object</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Types</string>
+ <key>scope</key>
+ <string>meta.type, meta.type-alias, support.type, entity.name.type</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorators</string>
+ <key>scope</key>
+ <string>meta.annotation variable.function, meta.annotation variable.annotation.function, meta.annotation punctuation.definition.annotation, meta.decorator, punctuation.decorator</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter, meta.function.parameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Built-ins</string>
+ <key>scope</key>
+ <string>constant.language, support.function.builtin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>entity.other.attribute-name.documentation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Preprocessor directives</string>
+ <key>scope</key>
+ <string>keyword.control.directive, punctuation.definition.directive</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Type parameters</string>
+ <key>scope</key>
+ <string>punctuation.definition.typeparameters</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Namespaces</string>
+ <key>scope</key>
+ <string>entity.name.namespace</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Property names (left hand assignments in json/yaml/css)</string>
+ <key>scope</key>
+ <string>support.type.property-name.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>This/Self keyword</string>
+ <key>scope</key>
+ <string>variable.language.this, variable.language.this punctuation.definition.variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Object properties</string>
+ <key>scope</key>
+ <string>variable.object.property</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>String template interpolation</string>
+ <key>scope</key>
+ <string>string.template variable, string variable</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>`new` as bold</string>
+ <key>scope</key>
+ <string>keyword.operator.new</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ extern keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.specifier.extern.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ scope resolution</string>
+ <key>scope</key>
+ <string>entity.name.scope-resolution.template.call.cpp, entity.name.scope-resolution.parameter.cpp, entity.name.scope-resolution.cpp, entity.name.scope-resolution.function.definition.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ doc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.doxygen</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C++ operators</string>
+ <key>scope</key>
+ <string>storage.modifier.reference.cpp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# Interpolated Strings</string>
+ <key>scope</key>
+ <string>meta.interpolation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>C# xml-style docs</string>
+ <key>scope</key>
+ <string>comment.block.documentation.cs</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Classes, reflecting the className color in JSX</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.class.css, entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Operators</string>
+ <key>scope</key>
+ <string>punctuation.separator.operator.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Pseudo classes</string>
+ <key>scope</key>
+ <string>source.css entity.other.attribute-name.pseudo-class</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css constant.other.unicode-range</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.css variable.parameter.url</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS vendored property names</string>
+ <key>scope</key>
+ <string>support.type.vendored.property-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Less/SCSS right-hand variables (@/$-prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-value variable, source.css meta.property-value variable.other.less, source.css meta.property-value variable.other.less punctuation.definition.variable.less, meta.definition.variable.scss</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS variables (--prefixed)</string>
+ <key>scope</key>
+ <string>source.css meta.property-list variable, meta.property-list variable.other.less, meta.property-list variable.other.less punctuation.definition.variable.less</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Percentage values, styled the same as numbers</string>
+ <key>scope</key>
+ <string>keyword.other.unit.percentage.css</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>CSS Attribute selectors, styled the same as strings</string>
+ <key>scope</key>
+ <string>source.css meta.attribute-selector</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML keys, other left-hand assignments</string>
+ <key>scope</key>
+ <string>keyword.other.definition.ini, punctuation.support.type.property-name.json, support.type.property-name.json, punctuation.support.type.property-name.toml, support.type.property-name.toml, entity.name.tag.yaml, punctuation.support.type.property-name.yaml, support.type.property-name.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSON/YAML constants</string>
+ <key>scope</key>
+ <string>constant.language.json, constant.language.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchors</string>
+ <key>scope</key>
+ <string>entity.name.type.anchor.yaml, variable.other.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML tables / ini groups</string>
+ <key>scope</key>
+ <string>support.type.property-name.table, entity.name.section.group-title.ini</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>TOML dates</string>
+ <key>scope</key>
+ <string>constant.other.time.datetime.offset.toml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML anchor puctuation</string>
+ <key>scope</key>
+ <string>punctuation.definition.anchor.yaml, punctuation.definition.alias.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>YAML triple dashes</string>
+ <key>scope</key>
+ <string>entity.other.document.begin.yaml</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markup Diff</string>
+ <key>scope</key>
+ <string>markup.changed.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff</string>
+ <key>scope</key>
+ <string>meta.diff.header.from-file, meta.diff.header.to-file, punctuation.definition.from-file.diff, punctuation.definition.to-file.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Inserted</string>
+ <key>scope</key>
+ <string>markup.inserted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Diff Deleted</string>
+ <key>scope</key>
+ <string>markup.deleted.diff</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv left-hand side assignments</string>
+ <key>scope</key>
+ <string>variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>dotenv reference to existing env variable</string>
+ <key>scope</key>
+ <string>string.quoted variable.other.env</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript functions</string>
+ <key>scope</key>
+ <string>support.function.builtin.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GDScript constants</string>
+ <key>scope</key>
+ <string>constant.language.gdscript</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Comment keywords</string>
+ <key>scope</key>
+ <string>comment meta.annotation.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>go:embed, go:build, etc.</string>
+ <key>scope</key>
+ <string>comment meta.annotation.parameters.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Go constants (nil, true, false)</string>
+ <key>scope</key>
+ <string>constant.language.go</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL variables</string>
+ <key>scope</key>
+ <string>variable.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL aliases</string>
+ <key>scope</key>
+ <string>string.unquoted.alias.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f2cdcd</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL enum members</string>
+ <key>scope</key>
+ <string>constant.character.enum.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>GraphQL field in types</string>
+ <key>scope</key>
+ <string>meta.objectvalues.graphql constant.object.key.graphql string.unquoted.graphql</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f2cdcd</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML DOCTYPE as keyword</string>
+ <key>scope</key>
+ <string>keyword.other.doctype, meta.tag.sgml.doctype punctuation.definition.tag, meta.tag.metadata.doctype entity.name.tag, meta.tag.metadata.doctype punctuation.definition.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML-like &lt;tags/&gt;</string>
+ <key>scope</key>
+ <string>entity.name.tag</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Special characters like &amp;amp;</string>
+ <key>scope</key>
+ <string>text.html constant.character.entity, text.html constant.character.entity punctuation, constant.character.entity.xml, constant.character.entity.xml punctuation, constant.character.entity.js.jsx, constant.charactger.entity.js.jsx punctuation, constant.character.entity.tsx, constant.character.entity.tsx punctuation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>HTML/XML tag attribute values</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Components</string>
+ <key>scope</key>
+ <string>support.class.component, support.class.component.jsx, support.class.component.tsx, support.class.component.vue</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Annotations</string>
+ <key>scope</key>
+ <string>punctuation.definition.annotation, storage.type.annotation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java enums</string>
+ <key>scope</key>
+ <string>constant.other.enum.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Java imports</string>
+ <key>scope</key>
+ <string>storage.modifier.import.java</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Javadoc</string>
+ <key>scope</key>
+ <string>comment.block.javadoc.java keyword.other.documentation.javadoc.java</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Exported Variable</string>
+ <key>scope</key>
+ <string>meta.export variable.other.readwrite.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JS/TS constants &amp; properties</string>
+ <key>scope</key>
+ <string>variable.other.constant.js, variable.other.constant.ts, variable.other.property.js, variable.other.property.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc; these are mainly params, so styled as such</string>
+ <key>scope</key>
+ <string>variable.other.jsdoc, comment.block.documentation variable.other</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>JSDoc keywords</string>
+ <key>scope</key>
+ <string>storage.type.class.jsdoc</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>support.type.object.console.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Node constants as keywords (module, etc.)</string>
+ <key>scope</key>
+ <string>support.constant.node, support.type.object.module.js</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>implements as keyword</string>
+ <key>scope</key>
+ <string>storage.modifier.implements</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Builtin types</string>
+ <key>scope</key>
+ <string>constant.language.null.js, constant.language.null.ts, constant.language.undefined.js, constant.language.undefined.ts, support.type.builtin.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>variable.parameter.generic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arrow functions</string>
+ <key>scope</key>
+ <string>keyword.declaration.function.arrow.js, storage.type.function.arrow.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Decorator punctuations (decorators inherit from blue functions, instead of styleguide peach)</string>
+ <key>scope</key>
+ <string>punctuation.decorator.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Extra JS/TS keywords</string>
+ <key>scope</key>
+ <string>keyword.operator.expression.in.js, keyword.operator.expression.in.ts, keyword.operator.expression.infer.ts, keyword.operator.expression.instanceof.js, keyword.operator.expression.instanceof.ts, keyword.operator.expression.is, keyword.operator.expression.keyof.ts, keyword.operator.expression.of.js, keyword.operator.expression.of.ts, keyword.operator.expression.typeof.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia macros</string>
+ <key>scope</key>
+ <string>support.function.macro.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia language constants (true, false)</string>
+ <key>scope</key>
+ <string>constant.language.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Julia other constants (these seem to be arguments inside arrays)</string>
+ <key>scope</key>
+ <string>constant.other.symbol.julia</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX preamble</string>
+ <key>scope</key>
+ <string>text.tex keyword.control.preamble</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX be functions</string>
+ <key>scope</key>
+ <string>text.tex support.function.be</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>LaTeX math</string>
+ <key>scope</key>
+ <string>constant.other.general.math.tex</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f2cdcd</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring keywords</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua storage.type.annotation.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Lua docstring variables</string>
+ <key>scope</key>
+ <string>comment.line.double-dash.documentation.lua entity.name.variable.lua, comment.line.double-dash.documentation.lua variable.lua</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.1.markdown punctuation.definition.heading.markdown, heading.1.markdown, markup.heading.atx.1.mdx, markup.heading.atx.1.mdx punctuation.definition.heading.mdx, markup.heading.setext.1.markdown, markup.heading.heading-0.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.2.markdown punctuation.definition.heading.markdown, heading.2.markdown, markup.heading.atx.2.mdx, markup.heading.atx.2.mdx punctuation.definition.heading.mdx, markup.heading.setext.2.markdown, markup.heading.heading-1.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.3.markdown punctuation.definition.heading.markdown, heading.3.markdown, markup.heading.atx.3.mdx, markup.heading.atx.3.mdx punctuation.definition.heading.mdx, markup.heading.heading-2.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.4.markdown punctuation.definition.heading.markdown, heading.4.markdown, markup.heading.atx.4.mdx, markup.heading.atx.4.mdx punctuation.definition.heading.mdx, markup.heading.heading-3.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.5.markdown punctuation.definition.heading.markdown, heading.5.markdown, markup.heading.atx.5.mdx, markup.heading.atx.5.mdx punctuation.definition.heading.mdx, markup.heading.heading-4.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>heading.6.markdown punctuation.definition.heading.markdown, heading.6.markdown, markup.heading.atx.6.mdx, markup.heading.atx.6.mdx punctuation.definition.heading.mdx, markup.heading.heading-5.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.bold</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ <key>fontStyle</key>
+ <string>bold</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.italic</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.strikethrough</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6adc8</string>
+ <key>fontStyle</key>
+ <string>strikethrough</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown auto links</string>
+ <key>scope</key>
+ <string>punctuation.definition.link, markup.underline.link</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown links</string>
+ <key>scope</key>
+ <string>text.html.markdown punctuation.definition.link.title, string.other.link.title.markdown, markup.link, punctuation.definition.constant.markdown, constant.other.reference.link.markdown, markup.substitution.attribute-reference</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#b4befe</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown code spans</string>
+ <key>scope</key>
+ <string>punctuation.definition.raw.markdown, markup.inline.raw.string.markdown, markup.raw.block.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backtick language identifier</string>
+ <key>scope</key>
+ <string>fenced_code.block.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown triple backticks</string>
+ <key>scope</key>
+ <string>markup.fenced_code.block punctuation.definition, markup.raw support.asciidoc</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#9399b2</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown quotes</string>
+ <key>scope</key>
+ <string>markup.quote, punctuation.definition.quote.begin</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown separators</string>
+ <key>scope</key>
+ <string>meta.separator.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Markdown list bullets</string>
+ <key>scope</key>
+ <string>punctuation.definition.list.begin.markdown, markup.list.bullet</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix attribute names</string>
+ <key>scope</key>
+ <string>entity.other.attribute-name.multipart.nix, entity.other.attribute-name.single.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix parameter names</string>
+ <key>scope</key>
+ <string>variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix interpolated parameter names</string>
+ <key>scope</key>
+ <string>meta.embedded variable.parameter.name.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#b4befe</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Nix paths</string>
+ <key>scope</key>
+ <string>string.unquoted.path.nix</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Attributes</string>
+ <key>scope</key>
+ <string>support.attribute.builtin, meta.attribute.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Parameters (needed for the leading dollar sign)</string>
+ <key>scope</key>
+ <string>meta.function.parameters.php punctuation.definition.variable.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP Constants (null, __FILE__, etc.)</string>
+ <key>scope</key>
+ <string>constant.language.php</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHP functions</string>
+ <key>scope</key>
+ <string>text.html.php support.function</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PHPdoc keywords</string>
+ <key>scope</key>
+ <string>keyword.other.phpdoc.php</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python argument functions reset to text, otherwise they inherit blue from function-call</string>
+ <key>scope</key>
+ <string>support.variable.magic.python, meta.function-call.arguments.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python double underscore functions</string>
+ <key>scope</key>
+ <string>support.function.magic.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python `self` keyword</string>
+ <key>scope</key>
+ <string>variable.parameter.function.language.special.self.python, variable.language.special.self.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python keyword flow/logical (for ... in)</string>
+ <key>scope</key>
+ <string>keyword.control.flow.python, keyword.operator.logical.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python storage type</string>
+ <key>scope</key>
+ <string>storage.type.function.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function support</string>
+ <key>scope</key>
+ <string>support.token.decorator.python, meta.function.decorator.identifier.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function calls</string>
+ <key>scope</key>
+ <string>meta.function-call.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python function decorators</string>
+ <key>scope</key>
+ <string>entity.name.function.decorator.python, punctuation.definition.decorator.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python placeholder reset to normal string</string>
+ <key>scope</key>
+ <string>constant.character.format.placeholder.other.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python exception &amp; builtins such as exit()</string>
+ <key>scope</key>
+ <string>support.type.exception.python, support.function.builtin.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>entity.name.type</string>
+ <key>scope</key>
+ <string>support.type.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>python constants (True/False)</string>
+ <key>scope</key>
+ <string>constant.language.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Arguments accessed later in the function body</string>
+ <key>scope</key>
+ <string>meta.indexed-name.python, meta.item-access.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python f-strings/binary/unicode storage types</string>
+ <key>scope</key>
+ <string>storage.type.string.python</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Python type hints</string>
+ <key>scope</key>
+ <string>meta.function.parameters.python</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex string begin/end in JS/TS</string>
+ <key>scope</key>
+ <string>string.regexp punctuation.definition.string.begin, string.regexp punctuation.definition.string.end</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex anchors (^, $)</string>
+ <key>scope</key>
+ <string>keyword.control.anchor.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex regular string match</string>
+ <key>scope</key>
+ <string>string.regexp.ts</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex group parenthesis &amp; backreference (\1, \2, \3, ...)</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.regexp, keyword.other.back-reference.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#a6e3a1</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character class []</string>
+ <key>scope</key>
+ <string>punctuation.definition.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex character classes (\d, \w, \s)</string>
+ <key>scope</key>
+ <string>constant.other.character-class.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex range</string>
+ <key>scope</key>
+ <string>constant.other.character-class.range.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5e0dc</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex quantifier</string>
+ <key>scope</key>
+ <string>keyword.operator.quantifier.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex constant/numeric</string>
+ <key>scope</key>
+ <string>constant.character.numeric.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Regex lookaheads, negative lookaheads, lookbehinds, negative lookbehinds</string>
+ <key>scope</key>
+ <string>punctuation.definition.group.no-capture.regexp, meta.assertion.look-ahead.regexp, meta.assertion.negative-look-ahead.regexp</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute</string>
+ <key>scope</key>
+ <string>meta.annotation.rust, meta.annotation.rust punctuation, meta.attribute.rust, punctuation.definition.attribute.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust attribute strings</string>
+ <key>scope</key>
+ <string>meta.attribute.rust string.quoted.double.rust, meta.attribute.rust string.quoted.single.char.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust keyword</string>
+ <key>scope</key>
+ <string>entity.name.function.macro.rules.rust, storage.type.module.rust, storage.modifier.rust, storage.type.struct.rust, storage.type.enum.rust, storage.type.trait.rust, storage.type.union.rust, storage.type.impl.rust, storage.type.rust, storage.type.function.rust, storage.type.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust u/i32, u/i64, etc.</string>
+ <key>scope</key>
+ <string>entity.name.type.numeric.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ <key>fontStyle</key>
+ <string/>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust generic</string>
+ <key>scope</key>
+ <string>meta.generic.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust impl</string>
+ <key>scope</key>
+ <string>entity.name.impl.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust module</string>
+ <key>scope</key>
+ <string>entity.name.module.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust trait</string>
+ <key>scope</key>
+ <string>entity.name.trait.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust struct</string>
+ <key>scope</key>
+ <string>storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust union</string>
+ <key>scope</key>
+ <string>entity.name.union.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust enum member</string>
+ <key>scope</key>
+ <string>meta.enum.rust storage.type.source.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust macro</string>
+ <key>scope</key>
+ <string>support.macro.rust, meta.macro.rust support.function.rust, entity.name.function.macro.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust lifetime</string>
+ <key>scope</key>
+ <string>storage.modifier.lifetime.rust, entity.name.type.lifetime</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust string formatting</string>
+ <key>scope</key>
+ <string>string.quoted.double.rust constant.other.placeholder.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust return type generic</string>
+ <key>scope</key>
+ <string>meta.function.return-type.rust meta.generic.rust storage.type.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust functions</string>
+ <key>scope</key>
+ <string>meta.function.call.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust angle brackets</string>
+ <key>scope</key>
+ <string>punctuation.brackets.angle.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89dceb</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust constants</string>
+ <key>scope</key>
+ <string>constant.other.caps.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust function parameters</string>
+ <key>scope</key>
+ <string>meta.function.definition.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#eba0ac</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust closure variables</string>
+ <key>scope</key>
+ <string>meta.function.call.rust variable.other.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust self</string>
+ <key>scope</key>
+ <string>variable.language.self.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Rust metavariable names</string>
+ <key>scope</key>
+ <string>variable.other.metavariable.name.rust, meta.macro.metavariable.rust keyword.operator.macro.dollar.rust</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang</string>
+ <key>scope</key>
+ <string>comment.line.shebang, comment.line.shebang punctuation.definition.comment, comment.line.shebang, punctuation.definition.comment.shebang.shell, meta.shebang.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell shebang command</string>
+ <key>scope</key>
+ <string>comment.line.shebang constant.language</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command</string>
+ <key>scope</key>
+ <string>meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation, meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell interpolated command variable</string>
+ <key>scope</key>
+ <string>meta.string meta.interpolation.parameter.shell variable.other.readwrite</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ <key>fontStyle</key>
+ <string>italic</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>source.shell punctuation.section.interpolation, punctuation.definition.evaluation.backticks.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell EOF</string>
+ <key>scope</key>
+ <string>entity.name.tag.heredoc.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Shell quoted variable</string>
+ <key>scope</key>
+ <string>string.quoted.double.shell variable.other.normal.shell</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cdd6f4</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.synopsis.man, markup.heading.title.man, markup.heading.other.man, markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#cba6f7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.commands.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#89b4fa</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.env.man</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f5c2e7</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>Man page options</string>
+ <key>scope</key>
+ <string>entity.name</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#94e2d5</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.1.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f38ba8</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.2.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#fab387</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>scope</key>
+ <string>markup.heading.markdown</string>
+ <key>settings</key>
+ <dict>
+ <key>foreground</key>
+ <string>#f9e2af</string>
+ </dict>
+ </dict>
+ </array>
+ </dict>
+</plist> \ No newline at end of file
diff --git a/ar/.config/blacklist.conf b/ar/.config/blacklist.conf
new file mode 100644
index 0000000..87c1780
--- /dev/null
+++ b/ar/.config/blacklist.conf
@@ -0,0 +1 @@
+blacklist snd_hda_codec_hdmi
diff --git a/ar/.config/calcurse/conf b/ar/.config/calcurse/conf
new file mode 100644
index 0000000..98b1a3b
--- /dev/null
+++ b/ar/.config/calcurse/conf
@@ -0,0 +1,33 @@
+appearance.calendarview=monthly
+appearance.compactpanels=no
+appearance.defaultpanel=calendar
+appearance.layout=3
+appearance.headerline=yes
+appearance.eventseparator=yes
+appearance.dayseparator=yes
+appearance.emptyline=yes
+appearance.emptyday=--
+appearance.notifybar=yes
+appearance.sidebarwidth=35
+appearance.theme=blue on default
+appearance.todoview=hide-completed
+appearance.headingpos=right-justified
+daemon.enable=no
+daemon.log=no
+format.inputdate=1
+format.notifydate=%a %F
+format.notifytime=%T
+format.appointmenttime=%H:%M
+format.outputdate=%D
+format.dayheading=%B %e, %Y
+general.autogc=no
+general.autosave=yes
+general.confirmdelete=yes
+general.confirmquit=yes
+general.firstdayofweek=monday
+general.multipledays=yes
+general.periodicsave=0
+general.systemevents=yes
+notification.command=printf '\a'
+notification.notifyall=flagged-only
+notification.warning=300
diff --git a/ar/.config/calcurse/keys b/ar/.config/calcurse/keys
new file mode 100644
index 0000000..49b4826
--- /dev/null
+++ b/ar/.config/calcurse/keys
@@ -0,0 +1,56 @@
+#
+# Calcurse keys configuration file
+#
+# In this file the keybindings used by Calcurse are defined.
+# It is generated automatically by Calcurse and is maintained
+# via the key configuration menu of the interactive user
+# interface. It should not be edited directly.
+
+generic-cancel ESC
+generic-select SPC
+generic-credits @
+generic-help ?
+generic-quit q Q
+generic-save s S ^S
+generic-reload R
+generic-copy c
+generic-paste p ^V
+generic-change-view TAB
+generic-prev-view KEY_BTAB
+generic-import i I
+generic-export x X
+generic-goto g G
+generic-other-cmd o O
+generic-config-menu C
+generic-redraw ^R
+generic-add-appt ^A
+generic-add-todo ^T
+generic-prev-day T ^H
+generic-next-day t ^L
+generic-prev-week W ^K
+generic-next-week w
+generic-prev-month M
+generic-next-month m
+generic-prev-year Y
+generic-next-year y
+generic-scroll-down ^N
+generic-scroll-up ^P
+generic-goto-today ^G
+generic-command :
+move-right l L RGT
+move-left h H LFT
+move-down j J DWN
+move-up k K UP
+start-of-week 0
+end-of-week $
+add-item a A
+del-item d D
+edit-item e E
+view-item v V RET
+pipe-item |
+flag-item !
+repeat r
+edit-note n N
+view-note >
+raise-priority +
+lower-priority -
diff --git a/ar/.config/crons b/ar/.config/crons
new file mode 100644
index 0000000..10cf41c
--- /dev/null
+++ b/ar/.config/crons
@@ -0,0 +1,14 @@
+*/3 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; /usr/bin/mailsync
+@daily export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; $HOME/.local/bin/cron/mediaup
+*/15 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; $HOME/.local/bin/cron/newsup
+0 */3 * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; $HOME/.local/bin/cron/checkup
+0 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; $HOME/.local/bin/taskwarrior-tui/tasks-sync
+0 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; NVIM_APPNAME=LazyVim nvim --headless "+Lazy! sync" +qa
+0 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; NVIM_APPNAME=LazyVim nvim --headless "+MasonUpdate" +qa
+0 0 * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; NVIM_APPNAME=NvChad nvim --headless "+Lazy! sync" +qa
+0 0 * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; NVIM_APPNAME=NvChad nvim --headless "+MasonUpdate" +qa
+0 0 * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; NVIM_APPNAME=TheSiahxyz nvim --headless "+Lazy! sync" +qa
+0 0 * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; NVIM_APPNAME=TheSiahxyz nvim --headless "+MasonUpdate" +qa
+0 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; /usr/bin/nvim --headless "+Lazy! sync" +qa
+0 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; /usr/bin/nvim --headless "+MasonUpdate" +qa
+@daily export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; /usr/bin/trash-empty 30
diff --git a/ar/.config/dunst/dunstrc b/ar/.config/dunst/dunstrc
new file mode 100644
index 0000000..84d9cc5
--- /dev/null
+++ b/ar/.config/dunst/dunstrc
@@ -0,0 +1,76 @@
+[global]
+ monitor = 0
+ follow = keyboard
+ width = (350, 600)
+ height = (0, 300)
+ origin = top-right
+ offset = 5x20
+ scale = 0
+
+ progress_bar = true
+ progress_bar_height = 10
+ progress_bar_frame_width = 1
+ progress_bar_min_width = 150
+ progress_bar_max_width = 400
+ progress_bar_corner_radius = 5
+
+ indicate_hidden = yes
+ shrink = no
+ separator_height = 5
+ separator_color = "#00000000"
+ padding = 5
+ horizontal_padding = 5
+ frame_width = 0
+ corner_radius = 0
+ sort = yes
+ idle_threshold = 120
+
+ font = Monospace 12
+ line_height = 0
+ markup = full
+ format = "<span weight='bold' font='12'>%s</span>\n%b"
+ alignment = left
+ vertical_alignment = center
+ show_age_threshold = 60
+ word_wrap = yes
+ ellipsize = middle
+ ignore_newline = no
+ stack_duplicates = true
+ hide_duplicate_count = false
+ show_indicators = yes
+ icon_position = left
+ min_icon_size = 50
+ max_icon_size = 60
+ sticky_history = yes
+ history_length = 20
+ always_run_script = true
+ title = Dunst
+ class = Dunst
+ ignore_dbusclose = false
+ force_xwayland = false
+ force_xinerama = false
+ mouse_left_click = do_action, close_current
+ mouse_middle_click = do_action, close_current
+ mouse_right_click = close_all
+ transparency = 0
+
+[experimental]
+ per_monitor_dpi = false
+
+[urgency_low]
+ background = "#1d2021"
+ foreground = "#928374"
+ frame_color = "#1d2021"
+ timeout = 3
+
+[urgency_normal]
+ background = "#458588"
+ foreground = "#ebdbb2"
+ frame_color = "#fabd2f"
+ timeout = 5
+
+[urgency_critical]
+ background = "#1cc24d"
+ foreground = "#ebdbb2"
+ frame_color = "#FB4934"
+ timeout = 100
diff --git a/ar/.config/fastfetch/config.jsonc b/ar/.config/fastfetch/config.jsonc
new file mode 100644
index 0000000..9d17de6
--- /dev/null
+++ b/ar/.config/fastfetch/config.jsonc
@@ -0,0 +1,167 @@
+// I literally was like most of you guys, a dotfiles scavenger, and got this
+// config from https://github.com/waytoabv/Dotfiles/blob/main/.config/fastfetch/config.jsonc
+// So the credit belongs to "waytoabv"
+//
+// kitten icat ~/github/dotfiles-latest/fastfetch/images/link-green.webp
+{
+ "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
+ "logo": {
+ // If working with tmux, you need to use kitty-icat instead of kitty-direct
+ // https://discord.com/channels/1005603569187160125/1324828708128821271/1327261766014599218
+ // "type": "kitty-icat",
+ "type": "auto",
+ "height": 20,
+ "width": 35,
+ "position": "left",
+ "padding": {
+ "top": 4,
+ "right": 3,
+ },
+ },
+ "display": {
+ "separator": " ",
+ },
+ "modules": [
+ "break",
+ {
+ "type": "custom",
+ "format": "┌──────────── Hardware ────────────┐",
+ "outputColor": "cyan",
+ },
+ {
+ "type": "host",
+ "key": " PC ",
+ "keyColor": "light_red",
+ },
+ {
+ "type": "cpu",
+ "key": "│ ├ ",
+ "keyColor": "red",
+ },
+ {
+ "type": "gpu",
+ "key": "│ ├ ",
+ "keyColor": "red",
+ },
+ {
+ "type": "disk",
+ "key": "│ ├ ",
+ "folders": "/",
+ "format": "{size-used} / {size-total} ({size-percentage})",
+ "keyColor": "red",
+ },
+ {
+ "type": "memory",
+ "key": "└ └ ",
+ "keyColor": "red",
+ },
+ {
+ "type": "custom",
+ "format": "└──────────────────────────────────┘",
+ "outputColor": "cyan",
+ },
+ {
+ "type": "custom",
+ "format": "┌──────────── Software ────────────┐",
+ "outputColor": "cyan",
+ },
+ {
+ "type": "os",
+ "key": " OS ",
+ "keyColor": "light_green",
+ },
+ {
+ "type": "kernel",
+ "key": "│ ├ ",
+ "keyColor": "green",
+ },
+ {
+ "type": "initsystem",
+ "key": "│ ├ ",
+ "keyColor": "green",
+ },
+ {
+ "type": "packages",
+ "key": "│ ├󰏖 ",
+ "keyColor": "green",
+ },
+ {
+ "type": "localip",
+ "key": "└ └󰩠 ",
+ "keyColor": "green",
+ },
+ {
+ "type": "lm",
+ "key": "󰍂 IN ",
+ "keyColor": "blue",
+ },
+ {
+ "type": "de",
+ "key": " DE ",
+ "keyColor": "blue",
+ },
+ {
+ "type": "wm",
+ "key": "│ ├ ",
+ "keyColor": "blue",
+ },
+ {
+ "key": "│ ├ ",
+ "keyColor": "blue",
+ "type": "command",
+ "text": "pgrep dwmblocks &> /dev/null && echo dwmblocks || echo Quartz Compositor",
+ },
+ {
+ "type": "terminal",
+ "key": "│ ├ ",
+ "keyColor": "blue",
+ },
+ {
+ "type": "terminalfont",
+ "key": "└ └ ",
+ "keyColor": "blue",
+ },
+ {
+ "type": "shell",
+ "key": "TERM",
+ "keyColor": "yellow",
+ },
+ {
+ "type": "editor",
+ "key": "│ ├󱩽 ",
+ "keyColor": "yellow",
+ },
+ {
+ "key": "└ └󱧷 ",
+ "keyColor": "yellow",
+ "type": "command",
+ "text": "echo $FILE_MANAGER",
+ },
+ {
+ "type": "custom",
+ "format": "└──────────────────────────────────┘",
+ "outputColor": "cyan",
+ },
+ {
+ "type": "custom",
+ "format": "┌────────── Age / Uptime ──────────┐",
+ "outputColor": "cyan",
+ },
+ {
+ "type": "command",
+ "key": " OS Age",
+ "keyColor": "magenta",
+ "text": "birth_install=$(stat -c %W /); current=$(date +%s); time_progression=$((current - birth_install)); days_difference=$((time_progression / 86400)); echo $days_difference days",
+ },
+ {
+ "type": "uptime",
+ "key": " Uptime",
+ "keyColor": "magenta",
+ },
+ {
+ "type": "custom",
+ "format": "└──────────────────────────────────┘",
+ "outputColor": "cyan",
+ },
+ ],
+}
diff --git a/ar/.config/fcitx5/conf/cached_layouts b/ar/.config/fcitx5/conf/cached_layouts
new file mode 100644
index 0000000..1eb8552
--- /dev/null
+++ b/ar/.config/fcitx5/conf/cached_layouts
@@ -0,0 +1,3670 @@
+[keyboard-bqn]
+Description="Keyboard - BQN"
+Language=en
+Label=bqn
+
+[keyboard-apl]
+Description="Keyboard - APL"
+Language=en
+Label=apl
+
+[keyboard-apl-dyalog]
+Description="Keyboard - APL - APL symbols (Dyalog APL)"
+Language=en
+Label="dlg (dyalog)"
+
+[keyboard-apl-sax]
+Description="Keyboard - APL - APL symbols (SAX, Sharp APL for Unix)"
+Language=en
+Label=sax
+
+[keyboard-apl-unified]
+Description="Keyboard - APL - APL symbols (unified)"
+Language=en
+Label="ufd (unified)"
+
+[keyboard-apl-apl2]
+Description="Keyboard - APL - APL symbols (IBM APL2)"
+Language=en
+Label=apl2
+
+[keyboard-apl-aplplusII]
+Description="Keyboard - APL - APL symbols (Manugistics APL*PLUS II)"
+Language=en
+Label="aplII (aplplusII)"
+
+[keyboard-apl-aplx]
+Description="Keyboard - APL - APL symbols (APLX unified)"
+Language=en
+Label=aplx
+
+[keyboard-ua]
+Description="Keyboard - Ukrainian"
+Language=uk
+Label=uk
+
+[keyboard-ua-phonetic]
+Description="Keyboard - Ukrainian - Ukrainian (phonetic)"
+Language=uk
+Label="ua (phonetic)"
+
+[keyboard-ua-typewriter]
+Description="Keyboard - Ukrainian - Ukrainian (typewriter)"
+Language=uk
+Label="ua (typewriter)"
+
+[keyboard-ua-winkeys]
+Description="Keyboard - Ukrainian - Ukrainian (Windows)"
+Language=uk
+Label="ua (winkeys)"
+
+[keyboard-ua-macOS]
+Description="Keyboard - Ukrainian - Ukrainian (macOS)"
+Language=uk
+Label="ua (macOS)"
+
+[keyboard-ua-legacy]
+Description="Keyboard - Ukrainian - Ukrainian (legacy)"
+Language=uk
+Label="ua (legacy)"
+
+[keyboard-ua-homophonic]
+Description="Keyboard - Ukrainian - Ukrainian (homophonic)"
+Language=uk
+Label="ua (homophonic)"
+
+[keyboard-ua-crh]
+Description="Keyboard - Ukrainian - Crimean Tatar (Turkish Q)"
+Language=crh
+Label=crh
+
+[keyboard-ua-crh_f]
+Description="Keyboard - Ukrainian - Crimean Tatar (Turkish F)"
+Language=crh
+Label="crh (crh_f)"
+
+[keyboard-ua-crh_alt]
+Description="Keyboard - Ukrainian - Crimean Tatar (Turkish Alt-Q)"
+Language=crh
+Label="crh (crh_alt)"
+
+[keyboard-ua-sun_type6]
+Description="Keyboard - Ukrainian - Ukrainian (Sun Type 6/7)"
+Language=uk
+Label="ua (sun_type6)"
+
+[keyboard-th]
+Description="Keyboard - Thai"
+Language=th
+Label=th
+
+[keyboard-th-tis]
+Description="Keyboard - Thai - Thai (TIS-820.2538)"
+Language=th
+Label="th (tis)"
+
+[keyboard-th-pat]
+Description="Keyboard - Thai - Thai (Pattachote)"
+Language=th
+Label="th (pat)"
+
+[keyboard-tz]
+Description="Keyboard - Swahili (Tanzania)"
+Language=sw
+Label=sw
+
+[keyboard-latam]
+Description="Keyboard - Spanish (Latin American)"
+Language=es
+Label=es
+
+[keyboard-latam-nodeadkeys]
+Description="Keyboard - Spanish (Latin American) - Spanish (Latin American, no dead keys)"
+Language=es
+Label="latam (nodeadkeys)"
+
+[keyboard-latam-deadtilde]
+Description="Keyboard - Spanish (Latin American) - Spanish (Latin American, dead tilde)"
+Language=es
+Label="latam (deadtilde)"
+
+[keyboard-latam-dvorak]
+Description="Keyboard - Spanish (Latin American) - Spanish (Latin American, Dvorak)"
+Language=es
+Label="latam (dvorak)"
+
+[keyboard-latam-colemak]
+Description="Keyboard - Spanish (Latin American) - Spanish (Latin American, Colemak)"
+Language=es
+Label="latam (colemak)"
+
+[keyboard-sk]
+Description="Keyboard - Slovak"
+Language=sk
+Label=sk
+
+[keyboard-sk-bksl]
+Description="Keyboard - Slovak - Slovak (extra backslash)"
+Language=sk
+Label="sk (bksl)"
+
+[keyboard-sk-qwerty]
+Description="Keyboard - Slovak - Slovak (QWERTY)"
+Language=sk
+Label="sk (qwerty)"
+
+[keyboard-sk-qwerty_bksl]
+Description="Keyboard - Slovak - Slovak (QWERTY, extra backslash)"
+Language=sk
+Label="sk (qwerty_bksl)"
+
+[keyboard-sk-acc]
+Description="Keyboard - Slovak - Slovak (ACC layout, only accented letters)"
+Language=sk
+Label="sk (acc)"
+
+[keyboard-sk-sun_type6]
+Description="Keyboard - Slovak - Slovak (Sun Type 6/7)"
+Language=sk
+Label="sk (sun_type6)"
+
+[keyboard-ru]
+Description="Keyboard - Russian"
+Language=ru
+Label=ru
+
+[keyboard-ru-phonetic]
+Description="Keyboard - Russian - Russian (phonetic)"
+Language=ru
+Label="ru (phonetic)"
+
+[keyboard-ru-phonetic_winkeys]
+Description="Keyboard - Russian - Russian (phonetic, Windows)"
+Language=ru
+Label="ru (phonetic_winkeys)"
+
+[keyboard-ru-phonetic_YAZHERTY]
+Description="Keyboard - Russian - Russian (phonetic, YAZHERTY)"
+Language=ru
+Label="ru (phonetic_YAZHERTY)"
+
+[keyboard-ru-phonetic_azerty]
+Description="Keyboard - Russian - Russian (phonetic, AZERTY)"
+Language=ru
+Label="ru (phonetic_azerty)"
+
+[keyboard-ru-phonetic_dvorak]
+Description="Keyboard - Russian - Russian (phonetic, Dvorak)"
+Language=ru
+Label="ru (phonetic_dvorak)"
+
+[keyboard-ru-typewriter]
+Description="Keyboard - Russian - Russian (typewriter)"
+Language=ru
+Label="ru (typewriter)"
+
+[keyboard-ru-ruchey_ru]
+Description="Keyboard - Russian - Russian (engineering, RU)"
+Language=ru
+Label="ru (ruchey_ru)"
+
+[keyboard-ru-ruchey_en]
+Description="Keyboard - Russian - Russian (engineering, EN)"
+Language=ru
+Label="en (ruchey_en)"
+
+[keyboard-ru-legacy]
+Description="Keyboard - Russian - Russian (legacy)"
+Language=ru
+Label="ru (legacy)"
+
+[keyboard-ru-typewriter-legacy]
+Description="Keyboard - Russian - Russian (typewriter, legacy)"
+Language=ru
+Label="ru (typewriter-legacy)"
+
+[keyboard-ru-dos]
+Description="Keyboard - Russian - Russian (DOS)"
+Language=ru
+Label="ru (dos)"
+
+[keyboard-ru-mac]
+Description="Keyboard - Russian - Russian (Macintosh)"
+Language=ru
+Label="ru (mac)"
+
+[keyboard-ru-ab]
+Description="Keyboard - Russian - Abkhazian (Russia)"
+Language=ab
+Label="ru (ab)"
+
+[keyboard-ru-bak]
+Description="Keyboard - Russian - Bashkirian"
+Language=ba
+Label="ru (bak)"
+
+[keyboard-ru-cv]
+Description="Keyboard - Russian - Chuvash"
+Language=cv
+Label="ru (cv)"
+
+[keyboard-ru-cv_latin]
+Description="Keyboard - Russian - Chuvash (Latin)"
+Language=cv
+Label="ru (cv_latin)"
+
+[keyboard-ru-xal]
+Description="Keyboard - Russian - Kalmyk"
+Language=xal
+Label="ru (xal)"
+
+[keyboard-ru-kom]
+Description="Keyboard - Russian - Komi"
+Language=kv
+Label="ru (kom)"
+
+[keyboard-ru-chm]
+Description="Keyboard - Russian - Mari"
+Language=chm
+Label="ru (chm)"
+
+[keyboard-ru-os_legacy]
+Description="Keyboard - Russian - Ossetian (legacy)"
+Language=os
+Label="ru (os_legacy)"
+
+[keyboard-ru-os_winkeys]
+Description="Keyboard - Russian - Ossetian (Windows)"
+Language=os
+Label="ru (os_winkeys)"
+
+[keyboard-ru-srp]
+Description="Keyboard - Russian - Serbian (Russia)"
+Language=ru
+Label="ru (srp)"
+
+[keyboard-ru-tt]
+Description="Keyboard - Russian - Tatar"
+Language=tt
+Label="ru (tt)"
+
+[keyboard-ru-udm]
+Description="Keyboard - Russian - Udmurt"
+Language=udm
+Label="ru (udm)"
+
+[keyboard-ru-sah]
+Description="Keyboard - Russian - Yakut"
+Language=sah
+Label="ru (sah)"
+
+[keyboard-ru-chu]
+Description="Keyboard - Russian - Church Slavonic"
+Language=cu
+Label="ru (chu)"
+
+[keyboard-ru-ruu]
+Description="Keyboard - Russian - Russian (plus Ukrainian and Belarusian letters)"
+Language=ru
+Label="ru (ruu)"
+
+[keyboard-ru-rulemak]
+Description="Keyboard - Russian - Russian (Rulemak, phonetic Colemak)"
+Language=ru
+Label="ru (rulemak)"
+
+[keyboard-ru-phonetic_mac]
+Description="Keyboard - Russian - Russian (phonetic, Macintosh)"
+Language=ru
+Label="ru (phonetic_mac)"
+
+[keyboard-ru-sun_type6]
+Description="Keyboard - Russian - Russian (Sun Type 6/7)"
+Language=ru
+Label="ru (sun_type6)"
+
+[keyboard-ru-unipunct]
+Description="Keyboard - Russian - Russian (with US punctuation)"
+Language=ru
+Label="ru (unipunct)"
+
+[keyboard-ru-gost-6431-75-48]
+Description="Keyboard - Russian - Russian (GOST 6431-75)"
+Language=ru
+Label="ru (gost-6431-75-48)"
+
+[keyboard-ru-gost-14289-88]
+Description="Keyboard - Russian - Russian (GOST 14289-88)"
+Language=ru
+Label="ru (gost-14289-88)"
+
+[keyboard-ru-prxn]
+Description="Keyboard - Russian - Russian (Polyglot and Reactionary)"
+Language=ru
+Label="ru (prxn)"
+
+[keyboard-ru-winkeys-p]
+Description="Keyboard - Russian - Russian (Programmer)"
+Language=ru
+Label=winkeys-p
+
+[keyboard-ru-typo]
+Description="Keyboard - Russian - Russian (plus typographic symbols)"
+Language=ru
+Label="ru (typo)"
+
+[keyboard-ru-rtu]
+Description="Keyboard - Russian - Russian (plus Tatar letters)"
+Language=ru
+Label="ru (rtu)"
+
+[keyboard-br]
+Description="Keyboard - Portuguese (Brazil)"
+Language=pt
+Label=pt
+
+[keyboard-br-nodeadkeys]
+Description="Keyboard - Portuguese (Brazil) - Portuguese (Brazil, no dead keys)"
+Language=pt
+Label="br (nodeadkeys)"
+
+[keyboard-br-dvorak]
+Description="Keyboard - Portuguese (Brazil) - Portuguese (Brazil, Dvorak)"
+Language=pt
+Label="br (dvorak)"
+
+[keyboard-br-nativo]
+Description="Keyboard - Portuguese (Brazil) - Portuguese (Brazil, Nativo)"
+Language=pt
+Label="br (nativo)"
+
+[keyboard-br-nativo-us]
+Description="Keyboard - Portuguese (Brazil) - Portuguese (Brazil, Nativo for US keyboards)"
+Language=pt
+Label="br (nativo-us)"
+
+[keyboard-br-thinkpad]
+Description="Keyboard - Portuguese (Brazil) - Portuguese (Brazil, IBM/Lenovo ThinkPad)"
+Language=pt
+Label="br (thinkpad)"
+
+[keyboard-br-nativo-epo]
+Description="Keyboard - Portuguese (Brazil) - Esperanto (Brazil, Nativo)"
+Language=eo
+Label="br (nativo-epo)"
+
+[keyboard-br-rus]
+Description="Keyboard - Portuguese (Brazil) - Russian (Brazil, phonetic)"
+Language=ru
+Label="ru (rus)"
+
+[keyboard-br-sun_type6]
+Description="Keyboard - Portuguese (Brazil) - Portuguese (Brazil, Sun Type 6/7)"
+Language=pt
+Label="br (sun_type6)"
+
+[keyboard-ro]
+Description="Keyboard - Romanian"
+Language=ro
+Label=ro
+
+[keyboard-ro-std]
+Description="Keyboard - Romanian - Romanian (standard)"
+Language=ro
+Label="ro (std)"
+
+[keyboard-ro-winkeys]
+Description="Keyboard - Romanian - Romanian (Windows)"
+Language=ro
+Label="ro (winkeys)"
+
+[keyboard-ro-crh_dobruja]
+Description="Keyboard - Romanian - Crimean Tatar (Dobruja Q)"
+Language=crh
+Label="crh (crh_dobruja)"
+
+[keyboard-ro-ergonomic]
+Description="Keyboard - Romanian - Romanian (ergonomic Touchtype)"
+Language=ro
+Label="ro (ergonomic)"
+
+[keyboard-ro-sun_type6]
+Description="Keyboard - Romanian - Romanian (Sun Type 6/7)"
+Language=ro
+Label="ro (sun_type6)"
+
+[keyboard-pl]
+Description="Keyboard - Polish"
+Language=pl
+Label=pl
+
+[keyboard-pl-legacy]
+Description="Keyboard - Polish - Polish (legacy)"
+Language=pl
+Label="pl (legacy)"
+
+[keyboard-pl-qwertz]
+Description="Keyboard - Polish - Polish (QWERTZ)"
+Language=pl
+Label="pl (qwertz)"
+
+[keyboard-pl-dvorak]
+Description="Keyboard - Polish - Polish (Dvorak)"
+Language=pl
+Label="pl (dvorak)"
+
+[keyboard-pl-dvorak_quotes]
+Description="Keyboard - Polish - Polish (Dvorak, with Polish quotes on quotemark key)"
+Language=pl
+Label="pl (dvorak_quotes)"
+
+[keyboard-pl-dvorak_altquotes]
+Description="Keyboard - Polish - Polish (Dvorak, with Polish quotes on key 1)"
+Language=pl
+Label="pl (dvorak_altquotes)"
+
+[keyboard-pl-dvp]
+Description="Keyboard - Polish - Polish (programmer Dvorak)"
+Language=pl
+Label="pl (dvp)"
+
+[keyboard-pl-csb]
+Description="Keyboard - Polish - Kashubian"
+Language=csb
+Label="pl (csb)"
+
+[keyboard-pl-szl]
+Description="Keyboard - Polish - Silesian"
+Language=szl
+Label="pl (szl)"
+
+[keyboard-pl-ru_phonetic_dvorak]
+Description="Keyboard - Polish - Russian (Poland, phonetic Dvorak)"
+Language=ru
+Label="ru (ru_phonetic_dvorak)"
+
+[keyboard-pl-intl]
+Description="Keyboard - Polish - Polish (intl., with dead keys)"
+Language=pl
+Label="pl (intl)"
+
+[keyboard-pl-colemak]
+Description="Keyboard - Polish - Polish (Colemak)"
+Language=pl
+Label="pl (colemak)"
+
+[keyboard-pl-colemak_dh_ansi]
+Description="Keyboard - Polish - Polish (Colemak-DH)"
+Language=pl
+Label="pl (colemak_dh_ansi)"
+
+[keyboard-pl-colemak_dh]
+Description="Keyboard - Polish - Polish (Colemak-DH ISO)"
+Language=pl
+Label="pl (colemak_dh)"
+
+[keyboard-pl-sun_type6]
+Description="Keyboard - Polish - Polish (Sun Type 6/7)"
+Language=pl
+Label="pl (sun_type6)"
+
+[keyboard-pl-glagolica]
+Description="Keyboard - Polish - Polish (Glagolica)"
+Language=pl
+Label="pl (glagolica)"
+
+[keyboard-pl-lefty]
+Description="Keyboard - Polish - Polish (lefty)"
+Language=pl
+Label="pl (lefty)"
+
+[keyboard-trans]
+Description="Keyboard - International Phonetic Alphabet"
+Language=
+Label=ipa
+
+[keyboard-trans-qwerty]
+Description="Keyboard - International Phonetic Alphabet - International Phonetic Alphabet (QWERTY)"
+Language=
+Label="trans (qwerty)"
+
+[keyboard-ir]
+Description="Keyboard - Persian"
+Language=fa
+Label=fa
+
+[keyboard-ir-pes_keypad]
+Description="Keyboard - Persian - Persian (with Persian keypad)"
+Language=fa
+Label="ir (pes_keypad)"
+
+[keyboard-ir-winkeys]
+Description="Keyboard - Persian - Persian (Windows)"
+Language=fa
+Label="ir (winkeys)"
+
+[keyboard-ir-azb]
+Description="Keyboard - Persian - Azerbaijani (Iran)"
+Language=azb
+Label=azb
+
+[keyboard-ir-ku]
+Description="Keyboard - Persian - Kurdish (Iran, Latin Q)"
+Language=ku
+Label=ku
+
+[keyboard-ir-ku_alt]
+Description="Keyboard - Persian - Kurdish (Iran, Latin Alt-Q)"
+Language=ku
+Label="ku (ku_alt)"
+
+[keyboard-ir-ku_f]
+Description="Keyboard - Persian - Kurdish (Iran, F)"
+Language=ku
+Label="ku (ku_f)"
+
+[keyboard-ir-ku_ara]
+Description="Keyboard - Persian - Kurdish (Iran, Arabic-Latin)"
+Language=ku
+Label="ku (ku_ara)"
+
+[keyboard-custom]
+Description="Keyboard - A user-defined custom Layout"
+Language=und
+Label=custom
+
+[keyboard-no]
+Description="Keyboard - Norwegian"
+Language=no
+Label=no
+
+[keyboard-no-nodeadkeys]
+Description="Keyboard - Norwegian - Norwegian (no dead keys)"
+Language=no
+Label="no (nodeadkeys)"
+
+[keyboard-no-winkeys]
+Description="Keyboard - Norwegian - Norwegian (Windows)"
+Language=no
+Label="no (winkeys)"
+
+[keyboard-no-mac]
+Description="Keyboard - Norwegian - Norwegian (Macintosh)"
+Language=no
+Label="no (mac)"
+
+[keyboard-no-mac_nodeadkeys]
+Description="Keyboard - Norwegian - Norwegian (Macintosh, no dead keys)"
+Language=no
+Label="no (mac_nodeadkeys)"
+
+[keyboard-no-colemak]
+Description="Keyboard - Norwegian - Norwegian (Colemak)"
+Language=no
+Label="no (colemak)"
+
+[keyboard-no-colemak_dh]
+Description="Keyboard - Norwegian - Norwegian (Colemak-DH)"
+Language=no
+Label="no (colemak_dh)"
+
+[keyboard-no-colemak_dh_wide]
+Description="Keyboard - Norwegian - Norwegian (Colemak-DH Wide)"
+Language=no
+Label="no (colemak_dh_wide)"
+
+[keyboard-no-dvorak]
+Description="Keyboard - Norwegian - Norwegian (Dvorak)"
+Language=no
+Label="no (dvorak)"
+
+[keyboard-no-smi]
+Description="Keyboard - Norwegian - Northern Saami (Norway)"
+Language=se
+Label="no (smi)"
+
+[keyboard-no-smi_nodeadkeys]
+Description="Keyboard - Norwegian - Northern Saami (Norway, no dead keys)"
+Language=se
+Label="no (smi_nodeadkeys)"
+
+[keyboard-no-sun_type6]
+Description="Keyboard - Norwegian - Norwegian (Sun Type 6/7)"
+Language=no
+Label="no (sun_type6)"
+
+[keyboard-gn]
+Description="Keyboard - N'Ko (AZERTY)"
+Language=nqo
+Label=nqo
+
+[keyboard-tm]
+Description="Keyboard - Turkmen"
+Language=tk
+Label=tk
+
+[keyboard-tm-alt]
+Description="Keyboard - Turkmen - Turkmen (Alt-Q)"
+Language=tk
+Label="tm (alt)"
+
+[keyboard-np]
+Description="Keyboard - Nepali"
+Language=ne
+Label=ne
+
+[keyboard-ancient]
+Description="Keyboard - Ancient"
+Language=got
+Label=xx
+
+[keyboard-ancient-got]
+Description="Keyboard - Ancient - Gothic"
+Language=got
+Label="ancient (got)"
+
+[keyboard-ancient-uga]
+Description="Keyboard - Ancient - Ugaritic"
+Language=uga
+Label="ancient (uga)"
+
+[keyboard-ancient-ave]
+Description="Keyboard - Ancient - Avestan"
+Language=ae
+Label="ancient (ave)"
+
+[keyboard-mt]
+Description="Keyboard - Maltese"
+Language=mt
+Label=mt
+
+[keyboard-mt-us]
+Description="Keyboard - Maltese - Maltese (US)"
+Language=mt
+Label="mt (us)"
+
+[keyboard-mt-alt-us]
+Description="Keyboard - Maltese - Maltese (US, with AltGr overrides)"
+Language=mt
+Label="mt (alt-us)"
+
+[keyboard-mt-alt-gb]
+Description="Keyboard - Maltese - Maltese (UK, with AltGr overrides)"
+Language=mt
+Label="mt (alt-gb)"
+
+[keyboard-pt]
+Description="Keyboard - Portuguese"
+Language=pt
+Label=pt
+
+[keyboard-pt-nodeadkeys]
+Description="Keyboard - Portuguese - Portuguese (no dead keys)"
+Language=pt
+Label="pt (nodeadkeys)"
+
+[keyboard-pt-mac]
+Description="Keyboard - Portuguese - Portuguese (Macintosh)"
+Language=pt
+Label="pt (mac)"
+
+[keyboard-pt-mac_nodeadkeys]
+Description="Keyboard - Portuguese - Portuguese (Macintosh, no dead keys)"
+Language=pt
+Label="pt (mac_nodeadkeys)"
+
+[keyboard-pt-nativo]
+Description="Keyboard - Portuguese - Portuguese (Nativo)"
+Language=pt
+Label="pt (nativo)"
+
+[keyboard-pt-nativo-us]
+Description="Keyboard - Portuguese - Portuguese (Nativo for US keyboards)"
+Language=pt
+Label="pt (nativo-us)"
+
+[keyboard-pt-nativo-epo]
+Description="Keyboard - Portuguese - Esperanto (Portugal, Nativo)"
+Language=eo
+Label="pt (nativo-epo)"
+
+[keyboard-pt-sun_type6]
+Description="Keyboard - Portuguese - Portuguese (Sun Type 6/7)"
+Language=pt
+Label="pt (sun_type6)"
+
+[keyboard-pt-colemak]
+Description="Keyboard - Portuguese - Portuguese (Colemak)"
+Language=pt
+Label="pt (colemak)"
+
+[keyboard-my]
+Description="Keyboard - Malay (Jawi, Arabic Keyboard)"
+Language=id
+Label=ms
+
+[keyboard-my-phonetic]
+Description="Keyboard - Malay (Jawi, Arabic Keyboard) - Malay (Jawi, phonetic)"
+Language=id
+Label="my (phonetic)"
+
+[keyboard-mk]
+Description="Keyboard - Macedonian"
+Language=mk
+Label=mk
+
+[keyboard-mk-nodeadkeys]
+Description="Keyboard - Macedonian - Macedonian (no dead keys)"
+Language=mk
+Label="mk (nodeadkeys)"
+
+[keyboard-kg]
+Description="Keyboard - Kyrgyz"
+Language=ky
+Label=ki
+
+[keyboard-kg-phonetic]
+Description="Keyboard - Kyrgyz - Kyrgyz (phonetic)"
+Language=ky
+Label="kg (phonetic)"
+
+[keyboard-tj]
+Description="Keyboard - Tajik"
+Language=tg
+Label=tg
+
+[keyboard-tj-legacy]
+Description="Keyboard - Tajik - Tajik (legacy)"
+Language=tg
+Label="tj (legacy)"
+
+[keyboard-mv]
+Description="Keyboard - Dhivehi"
+Language=dv
+Label=dv
+
+[keyboard-lk]
+Description="Keyboard - Sinhala (phonetic)"
+Language=si
+Label=si
+
+[keyboard-lk-us]
+Description="Keyboard - Sinhala (phonetic) - Sinhala (US)"
+Language=si
+Label="si (us)"
+
+[keyboard-lk-tam_unicode]
+Description="Keyboard - Sinhala (phonetic) - Tamil (Sri Lanka, TamilNet '99)"
+Language=ta
+Label="ta (tam_unicode)"
+
+[keyboard-lk-tam_TAB]
+Description="Keyboard - Sinhala (phonetic) - Tamil (Sri Lanka, TamilNet '99, TAB encoding)"
+Language=ta
+Label="lk (tam_TAB)"
+
+[keyboard-al]
+Description="Keyboard - Albanian"
+Language=sq
+Label=sq
+
+[keyboard-al-plisi]
+Description="Keyboard - Albanian - Albanian (Plisi)"
+Language=sq
+Label="al (plisi)"
+
+[keyboard-al-veqilharxhi]
+Description="Keyboard - Albanian - Albanian (Veqilharxhi)"
+Language=sq
+Label="al (veqilharxhi)"
+
+[keyboard-cz]
+Description="Keyboard - Czech"
+Language=cs
+Label=cs
+
+[keyboard-cz-bksl]
+Description="Keyboard - Czech - Czech (extra backslash)"
+Language=cs
+Label="cz (bksl)"
+
+[keyboard-cz-qwerty]
+Description="Keyboard - Czech - Czech (QWERTY)"
+Language=cs
+Label="cz (qwerty)"
+
+[keyboard-cz-qwerty_bksl]
+Description="Keyboard - Czech - Czech (QWERTY, extra backslash)"
+Language=cs
+Label="cz (qwerty_bksl)"
+
+[keyboard-cz-winkeys]
+Description="Keyboard - Czech - Czech (QWERTZ, Windows)"
+Language=cs
+Label="cz (winkeys)"
+
+[keyboard-cz-winkeys-qwerty]
+Description="Keyboard - Czech - Czech (QWERTY, Windows)"
+Language=cs
+Label="cz (winkeys-qwerty)"
+
+[keyboard-cz-qwerty-mac]
+Description="Keyboard - Czech - Czech (QWERTY, Macintosh)"
+Language=cs
+Label="cz (qwerty-mac)"
+
+[keyboard-cz-ucw]
+Description="Keyboard - Czech - Czech (UCW, only accented letters)"
+Language=cs
+Label="cz (ucw)"
+
+[keyboard-cz-dvorak-ucw]
+Description="Keyboard - Czech - Czech (US, Dvorak, UCW support)"
+Language=cs
+Label="cz (dvorak-ucw)"
+
+[keyboard-cz-rus]
+Description="Keyboard - Czech - Russian (Czechia, phonetic)"
+Language=ru
+Label="ru (rus)"
+
+[keyboard-cz-sun_type6]
+Description="Keyboard - Czech - Czech (Sun Type 6/7)"
+Language=cs
+Label="cz (sun_type6)"
+
+[keyboard-cz-prog]
+Description="Keyboard - Czech - Czech (programming)"
+Language=cs
+Label="cz (prog)"
+
+[keyboard-cz-prog_typo]
+Description="Keyboard - Czech - Czech (programming, typographic)"
+Language=cs
+Label="cz (prog_typo)"
+
+[keyboard-cz-coder]
+Description="Keyboard - Czech - Czech (coder)"
+Language=cs
+Label="cz (coder)"
+
+[keyboard-cz-colemak-ucw]
+Description="Keyboard - Czech - Czech (US, Colemak, UCW support)"
+Language=cs
+Label="cz (colemak-ucw)"
+
+[keyboard-brai]
+Description="Keyboard - Braille"
+Language=
+Label=brl
+
+[keyboard-brai-left_hand]
+Description="Keyboard - Braille - Braille (left-handed)"
+Language=
+Label="brai (left_hand)"
+
+[keyboard-brai-left_hand_invert]
+Description="Keyboard - Braille - Braille (left-handed inverted thumb)"
+Language=
+Label="brai (left_hand_invert)"
+
+[keyboard-brai-right_hand]
+Description="Keyboard - Braille - Braille (right-handed)"
+Language=
+Label="brai (right_hand)"
+
+[keyboard-brai-right_hand_invert]
+Description="Keyboard - Braille - Braille (right-handed inverted thumb)"
+Language=
+Label="brai (right_hand_invert)"
+
+[keyboard-se]
+Description="Keyboard - Swedish"
+Language=sv
+Label=sv
+
+[keyboard-se-nodeadkeys]
+Description="Keyboard - Swedish - Swedish (no dead keys)"
+Language=sv
+Label="se (nodeadkeys)"
+
+[keyboard-se-dvorak]
+Description="Keyboard - Swedish - Swedish (Dvorak)"
+Language=sv
+Label="se (dvorak)"
+
+[keyboard-se-us_dvorak]
+Description="Keyboard - Swedish - Swedish (Dvorak, intl.)"
+Language=sv
+Label="se (us_dvorak)"
+
+[keyboard-se-svdvorak]
+Description="Keyboard - Swedish - Swedish (Svdvorak)"
+Language=sv
+Label="se (svdvorak)"
+
+[keyboard-se-mac]
+Description="Keyboard - Swedish - Swedish (Macintosh)"
+Language=sv
+Label="se (mac)"
+
+[keyboard-se-us]
+Description="Keyboard - Swedish - Swedish (US)"
+Language=sv
+Label="se (us)"
+
+[keyboard-se-swl]
+Description="Keyboard - Swedish - Swedish Sign Language"
+Language=swl
+Label="se (swl)"
+
+[keyboard-se-smi]
+Description="Keyboard - Swedish - Northern Saami (Sweden)"
+Language=se
+Label="se (smi)"
+
+[keyboard-se-rus]
+Description="Keyboard - Swedish - Russian (Sweden, phonetic)"
+Language=ru
+Label="ru (rus)"
+
+[keyboard-se-dvorak_a5]
+Description="Keyboard - Swedish - Swedish (Dvorak A5)"
+Language=sv
+Label="se (dvorak_a5)"
+
+[keyboard-se-sun_type6]
+Description="Keyboard - Swedish - Swedish (Sun Type 6/7)"
+Language=sv
+Label="se (sun_type6)"
+
+[keyboard-se-ovd]
+Description="Keyboard - Swedish - Elfdalian (Sweden, with combining ogonek)"
+Language=ovd
+Label="se (ovd)"
+
+[keyboard-bg]
+Description="Keyboard - Bulgarian"
+Language=bg
+Label=bg
+
+[keyboard-bg-phonetic]
+Description="Keyboard - Bulgarian - Bulgarian (traditional phonetic)"
+Language=bg
+Label="bg (phonetic)"
+
+[keyboard-bg-bas_phonetic]
+Description="Keyboard - Bulgarian - Bulgarian (new phonetic)"
+Language=bg
+Label="bg (bas_phonetic)"
+
+[keyboard-bg-bekl]
+Description="Keyboard - Bulgarian - Bulgarian (enhanced)"
+Language=bg
+Label="bg (bekl)"
+
+[keyboard-pk]
+Description="Keyboard - Urdu (Pakistan)"
+Language=ur
+Label=ur
+
+[keyboard-pk-urd-crulp]
+Description="Keyboard - Urdu (Pakistan) - Urdu (Pakistan, CRULP)"
+Language=ur
+Label="pk (urd-crulp)"
+
+[keyboard-pk-urd-nla]
+Description="Keyboard - Urdu (Pakistan) - Urdu (Pakistan, NLA)"
+Language=ur
+Label="pk (urd-nla)"
+
+[keyboard-pk-ara]
+Description="Keyboard - Urdu (Pakistan) - Arabic (Pakistan)"
+Language=ar
+Label="ar (ara)"
+
+[keyboard-pk-snd]
+Description="Keyboard - Urdu (Pakistan) - Sindhi"
+Language=sd
+Label="sd (snd)"
+
+[keyboard-pk-urd-navees]
+Description="Keyboard - Urdu (Pakistan) - Urdu (Pakistan, Navees)"
+Language=ur
+Label="pk (urd-navees)"
+
+[keyboard-au]
+Description="Keyboard - English (Australia)"
+Language=en
+Label=en
+
+[keyboard-mn]
+Description="Keyboard - Mongolian"
+Language=mn
+Label=mn
+
+[keyboard-dz]
+Description="Keyboard - Berber (Algeria, Latin)"
+Language=tzm
+Label=kab
+
+[keyboard-dz-ber]
+Description="Keyboard - Berber (Algeria, Latin) - Berber (Algeria, Tifinagh)"
+Language=kab
+Label="kab (ber)"
+
+[keyboard-dz-azerty-deadkeys]
+Description="Keyboard - Berber (Algeria, Latin) - Kabyle (AZERTY, with dead keys)"
+Language=kab
+Label="kab (azerty-deadkeys)"
+
+[keyboard-dz-qwerty-gb-deadkeys]
+Description="Keyboard - Berber (Algeria, Latin) - Kabyle (QWERTY, UK, with dead keys)"
+Language=kab
+Label="kab (qwerty-gb-deadkeys)"
+
+[keyboard-dz-qwerty-us-deadkeys]
+Description="Keyboard - Berber (Algeria, Latin) - Kabyle (QWERTY, US, with dead keys)"
+Language=kab
+Label="kab (qwerty-us-deadkeys)"
+
+[keyboard-dz-ar]
+Description="Keyboard - Berber (Algeria, Latin) - Arabic (Algeria)"
+Language=ar
+Label=ar
+
+[keyboard-me]
+Description="Keyboard - Montenegrin"
+Language=sr
+Label=sr
+
+[keyboard-me-cyrillic]
+Description="Keyboard - Montenegrin - Montenegrin (Cyrillic)"
+Language=sr
+Label="me (cyrillic)"
+
+[keyboard-me-cyrillicyz]
+Description="Keyboard - Montenegrin - Montenegrin (Cyrillic, ZE and ZHE swapped)"
+Language=sr
+Label="me (cyrillicyz)"
+
+[keyboard-me-cyrillicalternatequotes]
+Description="Keyboard - Montenegrin - Montenegrin (Cyrillic, with guillemets)"
+Language=sr
+Label="me (cyrillicalternatequotes)"
+
+[keyboard-me-latinunicode]
+Description="Keyboard - Montenegrin - Montenegrin (Latin, Unicode)"
+Language=sr
+Label="me (latinunicode)"
+
+[keyboard-me-latinyz]
+Description="Keyboard - Montenegrin - Montenegrin (Latin, QWERTY)"
+Language=sr
+Label="me (latinyz)"
+
+[keyboard-me-latinunicodeyz]
+Description="Keyboard - Montenegrin - Montenegrin (Latin, Unicode, QWERTY)"
+Language=sr
+Label="me (latinunicodeyz)"
+
+[keyboard-me-latinalternatequotes]
+Description="Keyboard - Montenegrin - Montenegrin (Latin, with guillemets)"
+Language=sr
+Label="me (latinalternatequotes)"
+
+[keyboard-lv]
+Description="Keyboard - Latvian"
+Language=lv
+Label=lv
+
+[keyboard-lv-apostrophe]
+Description="Keyboard - Latvian - Latvian (apostrophe)"
+Language=lv
+Label="lv (apostrophe)"
+
+[keyboard-lv-tilde]
+Description="Keyboard - Latvian - Latvian (tilde)"
+Language=lv
+Label="lv (tilde)"
+
+[keyboard-lv-fkey]
+Description="Keyboard - Latvian - Latvian (F)"
+Language=lv
+Label="lv (fkey)"
+
+[keyboard-lv-modern]
+Description="Keyboard - Latvian - Latvian (Modern Latin)"
+Language=lv
+Label="lv (modern)"
+
+[keyboard-lv-modern-cyr]
+Description="Keyboard - Latvian - Latvian (Modern Cyrillic)"
+Language=lv
+Label="lv (modern-cyr)"
+
+[keyboard-lv-ergonomic]
+Description="Keyboard - Latvian - Latvian (ergonomic, ŪGJRMV)"
+Language=lv
+Label="lv (ergonomic)"
+
+[keyboard-lv-adapted]
+Description="Keyboard - Latvian - Latvian (adapted)"
+Language=lv
+Label="lv (adapted)"
+
+[keyboard-lv-dvorak]
+Description="Keyboard - Latvian - Latvian (Dvorak)"
+Language=lv
+Label="lv (dvorak)"
+
+[keyboard-lv-ykeydvorak]
+Description="Keyboard - Latvian - Latvian (Dvorak, with Y)"
+Language=lv
+Label="lv (ykeydvorak)"
+
+[keyboard-lv-minuskeydvorak]
+Description="Keyboard - Latvian - Latvian (Dvorak, with minus)"
+Language=lv
+Label="lv (minuskeydvorak)"
+
+[keyboard-lv-dvorakprogr]
+Description="Keyboard - Latvian - Latvian (programmer Dvorak)"
+Language=lv
+Label="lv (dvorakprogr)"
+
+[keyboard-lv-ykeydvorakprogr]
+Description="Keyboard - Latvian - Latvian (programmer Dvorak, with Y)"
+Language=lv
+Label="lv (ykeydvorakprogr)"
+
+[keyboard-lv-minuskeydvorakprogr]
+Description="Keyboard - Latvian - Latvian (programmer Dvorak, with minus)"
+Language=lv
+Label="lv (minuskeydvorakprogr)"
+
+[keyboard-lv-colemak]
+Description="Keyboard - Latvian - Latvian (Colemak)"
+Language=lv
+Label="lv (colemak)"
+
+[keyboard-lv-apostrophecolemak]
+Description="Keyboard - Latvian - Latvian (Colemak, with apostrophe)"
+Language=lv
+Label="lv (apostrophecolemak)"
+
+[keyboard-lv-sun_type6]
+Description="Keyboard - Latvian - Latvian (Sun Type 6/7)"
+Language=lv
+Label="lv (sun_type6)"
+
+[keyboard-lv-apostrophe-deadquotes]
+Description="Keyboard - Latvian - Latvian (apostrophe, dead quotes)"
+Language=lv
+Label="lv (apostrophe-deadquotes)"
+
+[keyboard-ba]
+Description="Keyboard - Bosnian"
+Language=bs
+Label=bs
+
+[keyboard-ba-alternatequotes]
+Description="Keyboard - Bosnian - Bosnian (with guillemets)"
+Language=bs
+Label="ba (alternatequotes)"
+
+[keyboard-ba-unicode]
+Description="Keyboard - Bosnian - Bosnian (with Bosnian digraphs)"
+Language=bs
+Label="ba (unicode)"
+
+[keyboard-ba-unicodeus]
+Description="Keyboard - Bosnian - Bosnian (US, with Bosnian digraphs)"
+Language=bs
+Label="ba (unicodeus)"
+
+[keyboard-ba-us]
+Description="Keyboard - Bosnian - Bosnian (US)"
+Language=bs
+Label="ba (us)"
+
+[keyboard-tw]
+Description="Keyboard - Taiwanese"
+Language=
+Label=zh
+
+[keyboard-tw-indigenous]
+Description="Keyboard - Taiwanese - Taiwanese (indigenous)"
+Language=tay
+Label="tw (indigenous)"
+
+[keyboard-tw-saisiyat]
+Description="Keyboard - Taiwanese - Saisiyat (Taiwan)"
+Language=xsy
+Label="xsy (saisiyat)"
+
+[keyboard-rs]
+Description="Keyboard - Serbian"
+Language=sr
+Label=sr
+
+[keyboard-rs-alternatequotes]
+Description="Keyboard - Serbian - Serbian (Cyrillic, with guillemets)"
+Language=sr
+Label="rs (alternatequotes)"
+
+[keyboard-rs-yz]
+Description="Keyboard - Serbian - Serbian (Cyrillic, ZE and ZHE swapped)"
+Language=sr
+Label="rs (yz)"
+
+[keyboard-rs-latin]
+Description="Keyboard - Serbian - Serbian (Latin)"
+Language=sr
+Label="rs (latin)"
+
+[keyboard-rs-latinalternatequotes]
+Description="Keyboard - Serbian - Serbian (Latin, with guillemets)"
+Language=sr
+Label="rs (latinalternatequotes)"
+
+[keyboard-rs-latinunicode]
+Description="Keyboard - Serbian - Serbian (Latin, Unicode)"
+Language=sr
+Label="rs (latinunicode)"
+
+[keyboard-rs-latinyz]
+Description="Keyboard - Serbian - Serbian (Latin, QWERTY)"
+Language=sr
+Label="rs (latinyz)"
+
+[keyboard-rs-latinunicodeyz]
+Description="Keyboard - Serbian - Serbian (Latin, Unicode, QWERTY)"
+Language=sr
+Label="rs (latinunicodeyz)"
+
+[keyboard-rs-rue]
+Description="Keyboard - Serbian - Pannonian Rusyn"
+Language=rue
+Label="rs (rue)"
+
+[keyboard-rs-combiningkeys]
+Description="Keyboard - Serbian - Serbian (combining accents instead of dead keys)"
+Language=sr
+Label="rs (combiningkeys)"
+
+[keyboard-dk]
+Description="Keyboard - Danish"
+Language=da
+Label=da
+
+[keyboard-dk-nodeadkeys]
+Description="Keyboard - Danish - Danish (no dead keys)"
+Language=da
+Label="dk (nodeadkeys)"
+
+[keyboard-dk-winkeys]
+Description="Keyboard - Danish - Danish (Windows)"
+Language=da
+Label="dk (winkeys)"
+
+[keyboard-dk-mac]
+Description="Keyboard - Danish - Danish (Macintosh)"
+Language=da
+Label="dk (mac)"
+
+[keyboard-dk-mac_nodeadkeys]
+Description="Keyboard - Danish - Danish (Macintosh, no dead keys)"
+Language=da
+Label="dk (mac_nodeadkeys)"
+
+[keyboard-dk-dvorak]
+Description="Keyboard - Danish - Danish (Dvorak)"
+Language=da
+Label="dk (dvorak)"
+
+[keyboard-dk-sun_type6]
+Description="Keyboard - Danish - Danish (Sun Type 6/7)"
+Language=da
+Label="dk (sun_type6)"
+
+[keyboard-bw]
+Description="Keyboard - Tswana"
+Language=tn
+Label=tn
+
+[keyboard-kr]
+Description="Keyboard - Korean"
+Language=ko
+Label=ko
+
+[keyboard-kr-kr104]
+Description="Keyboard - Korean - Korean (101/104-key compatible)"
+Language=ko
+Label="kr (kr104)"
+
+[keyboard-kr-sun_type6]
+Description="Keyboard - Korean - Korean (Sun Type 6/7)"
+Language=ko
+Label="kr (sun_type6)"
+
+[keyboard-nl]
+Description="Keyboard - Dutch"
+Language=nl
+Label=nl
+
+[keyboard-nl-us]
+Description="Keyboard - Dutch - Dutch (US)"
+Language=nl
+Label="nl (us)"
+
+[keyboard-nl-mac]
+Description="Keyboard - Dutch - Dutch (Macintosh)"
+Language=nl
+Label="nl (mac)"
+
+[keyboard-nl-std]
+Description="Keyboard - Dutch - Dutch (standard)"
+Language=nl
+Label="nl (std)"
+
+[keyboard-nl-sun_type6]
+Description="Keyboard - Dutch - Dutch (Sun Type 6/7)"
+Language=nl
+Label="nl (sun_type6)"
+
+[keyboard-et]
+Description="Keyboard - Amharic"
+Language=am
+Label=am
+
+[keyboard-be]
+Description="Keyboard - Belgian"
+Language=de
+Label=be
+
+[keyboard-be-oss]
+Description="Keyboard - Belgian - Belgian (alt.)"
+Language=de
+Label="be (oss)"
+
+[keyboard-be-oss_latin9]
+Description="Keyboard - Belgian - Belgian (Latin-9 only, alt.)"
+Language=de
+Label="be (oss_latin9)"
+
+[keyboard-be-iso-alternate]
+Description="Keyboard - Belgian - Belgian (ISO, alt.)"
+Language=de
+Label="be (iso-alternate)"
+
+[keyboard-be-nodeadkeys]
+Description="Keyboard - Belgian - Belgian (no dead keys)"
+Language=de
+Label="be (nodeadkeys)"
+
+[keyboard-be-wang]
+Description="Keyboard - Belgian - Belgian (Wang 724 AZERTY)"
+Language=de
+Label="be (wang)"
+
+[keyboard-be-sun_type6]
+Description="Keyboard - Belgian - Belgian (Sun Type 6/7)"
+Language=de
+Label="be (sun_type6)"
+
+[keyboard-la]
+Description="Keyboard - Lao"
+Language=lo
+Label=lo
+
+[keyboard-la-stea]
+Description="Keyboard - Lao - Lao (STEA)"
+Language=lo
+Label="la (stea)"
+
+[keyboard-bt]
+Description="Keyboard - Dzongkha"
+Language=dz
+Label=dz
+
+[keyboard-mm]
+Description="Keyboard - Burmese"
+Language=my
+Label=my
+
+[keyboard-mm-zawgyi]
+Description="Keyboard - Burmese - Burmese (Zawgyi)"
+Language=my
+Label="my-zwg (zawgyi)"
+
+[keyboard-mm-mnw]
+Description="Keyboard - Burmese - Mon"
+Language=mnw
+Label=mnw
+
+[keyboard-mm-mnw-a1]
+Description="Keyboard - Burmese - Mon (A1)"
+Language=mnw
+Label="mnw (mnw-a1)"
+
+[keyboard-mm-shn]
+Description="Keyboard - Burmese - Shan"
+Language=shn
+Label=shn
+
+[keyboard-mm-zgt]
+Description="Keyboard - Burmese - Shan (Zawgyi)"
+Language=shn
+Label="shn-zwg (zgt)"
+
+[keyboard-si]
+Description="Keyboard - Slovenian"
+Language=sl
+Label=sl
+
+[keyboard-si-alternatequotes]
+Description="Keyboard - Slovenian - Slovenian (with guillemets)"
+Language=sl
+Label="si (alternatequotes)"
+
+[keyboard-si-us]
+Description="Keyboard - Slovenian - Slovenian (US)"
+Language=sl
+Label="si (us)"
+
+[keyboard-am]
+Description="Keyboard - Armenian"
+Language=hy
+Label=hy
+
+[keyboard-am-phonetic]
+Description="Keyboard - Armenian - Armenian (phonetic)"
+Language=hy
+Label="am (phonetic)"
+
+[keyboard-am-phonetic-alt]
+Description="Keyboard - Armenian - Armenian (alt. phonetic)"
+Language=hy
+Label="am (phonetic-alt)"
+
+[keyboard-am-eastern]
+Description="Keyboard - Armenian - Armenian (eastern)"
+Language=hy
+Label="am (eastern)"
+
+[keyboard-am-eastern-alt]
+Description="Keyboard - Armenian - Armenian (alt. eastern)"
+Language=hy
+Label="am (eastern-alt)"
+
+[keyboard-am-western]
+Description="Keyboard - Armenian - Armenian (western)"
+Language=hy
+Label="am (western)"
+
+[keyboard-am-olpc-phonetic]
+Description="Keyboard - Armenian - Armenian (OLPC, phonetic)"
+Language=hy
+Label="am (olpc-phonetic)"
+
+[keyboard-by]
+Description="Keyboard - Belarusian"
+Language=be
+Label=by
+
+[keyboard-by-legacy]
+Description="Keyboard - Belarusian - Belarusian (legacy)"
+Language=be
+Label="by (legacy)"
+
+[keyboard-by-latin]
+Description="Keyboard - Belarusian - Belarusian (Latin)"
+Language=be
+Label="by (latin)"
+
+[keyboard-by-intl]
+Description="Keyboard - Belarusian - Belarusian (intl.)"
+Language=be
+Label="by (intl)"
+
+[keyboard-by-phonetic]
+Description="Keyboard - Belarusian - Belarusian (phonetic)"
+Language=be
+Label="by (phonetic)"
+
+[keyboard-by-ru]
+Description="Keyboard - Belarusian - Russian (Belarus)"
+Language=ru
+Label="by (ru)"
+
+[keyboard-vn]
+Description="Keyboard - Vietnamese"
+Language=vi
+Label=vi
+
+[keyboard-vn-us]
+Description="Keyboard - Vietnamese - Vietnamese (US)"
+Language=vi
+Label="vn (us)"
+
+[keyboard-vn-fr]
+Description="Keyboard - Vietnamese - Vietnamese (France)"
+Language=vi
+Label="vn (fr)"
+
+[keyboard-vn-aderty]
+Description="Keyboard - Vietnamese - Vietnamese (AÐERTY)"
+Language=vi
+Label="vn (aderty)"
+
+[keyboard-vn-qderty]
+Description="Keyboard - Vietnamese - Vietnamese (QĐERTY)"
+Language=vi
+Label="vn (qderty)"
+
+[keyboard-ml]
+Description="Keyboard - Bambara"
+Language=bm
+Label=bm
+
+[keyboard-ml-fr-oss]
+Description="Keyboard - Bambara - French (Mali, alt.)"
+Language=fr
+Label="fr (fr-oss)"
+
+[keyboard-ml-us-mac]
+Description="Keyboard - Bambara - English (Mali, US, Macintosh)"
+Language=en
+Label="en (us-mac)"
+
+[keyboard-ml-us-intl]
+Description="Keyboard - Bambara - English (Mali, US, intl.)"
+Language=en
+Label="en (us-intl)"
+
+[keyboard-ara]
+Description="Keyboard - Arabic"
+Language=ar
+Label=ar
+
+[keyboard-ara-digits]
+Description="Keyboard - Arabic - Arabic (Eastern Arabic numerals)"
+Language=ar
+Label="ara (digits)"
+
+[keyboard-ara-azerty]
+Description="Keyboard - Arabic - Arabic (AZERTY)"
+Language=ar
+Label="ara (azerty)"
+
+[keyboard-ara-azerty_digits]
+Description="Keyboard - Arabic - Arabic (AZERTY, Eastern Arabic numerals)"
+Language=ar
+Label="ara (azerty_digits)"
+
+[keyboard-ara-buckwalter]
+Description="Keyboard - Arabic - Arabic (Buckwalter)"
+Language=ar
+Label="ara (buckwalter)"
+
+[keyboard-ara-mac]
+Description="Keyboard - Arabic - Arabic (Macintosh)"
+Language=ar
+Label="ara (mac)"
+
+[keyboard-ara-mac-phonetic]
+Description="Keyboard - Arabic - Arabic (Macintosh, phonetic)"
+Language=ar
+Label="ara (mac-phonetic)"
+
+[keyboard-ara-olpc]
+Description="Keyboard - Arabic - Arabic (OLPC)"
+Language=ar
+Label="ara (olpc)"
+
+[keyboard-ara-sun_type6]
+Description="Keyboard - Arabic - Arabic (Sun Type 6/7)"
+Language=ar
+Label="ara (sun_type6)"
+
+[keyboard-ara-basic_ext]
+Description="Keyboard - Arabic - Arabic (Arabic numerals, extensions in the 4th level)"
+Language=ar
+Label="ara (basic_ext)"
+
+[keyboard-ara-basic_ext_digits]
+Description="Keyboard - Arabic - Arabic (Eastern Arabic numerals, extensions in the 4th level)"
+Language=ar
+Label="ara (basic_ext_digits)"
+
+[keyboard-ara-ergoarabic]
+Description="Keyboard - Arabic - Arabic (ErgoArabic)"
+Language=ar
+Label="ara (ergoarabic)"
+
+[keyboard-ie]
+Description="Keyboard - Irish"
+Language=en
+Label=ie
+
+[keyboard-ie-UnicodeExpert]
+Description="Keyboard - Irish - Irish (UnicodeExpert)"
+Language=en
+Label="ie (UnicodeExpert)"
+
+[keyboard-ie-CloGaelach]
+Description="Keyboard - Irish - CloGaelach"
+Language=ga
+Label="ie (CloGaelach)"
+
+[keyboard-ie-ogam]
+Description="Keyboard - Irish - Ogham"
+Language=sga
+Label="ie (ogam)"
+
+[keyboard-ie-ogam_is434]
+Description="Keyboard - Irish - Ogham (IS434)"
+Language=sga
+Label="ie (ogam_is434)"
+
+[keyboard-cm]
+Description="Keyboard - English (Cameroon)"
+Language=en
+Label=cm
+
+[keyboard-cm-french]
+Description="Keyboard - English (Cameroon) - French (Cameroon)"
+Language=fr
+Label="fr (french)"
+
+[keyboard-cm-qwerty]
+Description="Keyboard - English (Cameroon) - Cameroon Multilingual (QWERTY, intl.)"
+Language=en
+Label="cm (qwerty)"
+
+[keyboard-cm-azerty]
+Description="Keyboard - English (Cameroon) - Cameroon (AZERTY, intl.)"
+Language=fr
+Label="cm (azerty)"
+
+[keyboard-cm-dvorak]
+Description="Keyboard - English (Cameroon) - Cameroon (Dvorak, intl.)"
+Language=en
+Label="cm (dvorak)"
+
+[keyboard-cm-mmuock]
+Description="Keyboard - English (Cameroon) - Mmuock"
+Language=en
+Label="cm (mmuock)"
+
+[keyboard-iq]
+Description="Keyboard - Arabic (Iraq)"
+Language=ar
+Label=ar
+
+[keyboard-iq-ku]
+Description="Keyboard - Arabic (Iraq) - Kurdish (Iraq, Latin Q)"
+Language=ku
+Label=ku
+
+[keyboard-iq-ku_alt]
+Description="Keyboard - Arabic (Iraq) - Kurdish (Iraq, Latin Alt-Q)"
+Language=ku
+Label="ku (ku_alt)"
+
+[keyboard-iq-ku_f]
+Description="Keyboard - Arabic (Iraq) - Kurdish (Iraq, F)"
+Language=ku
+Label="ku (ku_f)"
+
+[keyboard-iq-ku_ara]
+Description="Keyboard - Arabic (Iraq) - Kurdish (Iraq, Arabic-Latin)"
+Language=ku
+Label="ku (ku_ara)"
+
+[keyboard-af]
+Description="Keyboard - Dari"
+Language=prs
+Label=fa
+
+[keyboard-af-ps]
+Description="Keyboard - Dari - Pashto"
+Language=ps
+Label=ps
+
+[keyboard-af-uz]
+Description="Keyboard - Dari - Uzbek (Afghanistan)"
+Language=uz
+Label=uz
+
+[keyboard-af-fa-olpc]
+Description="Keyboard - Dari - Dari (Afghanistan, OLPC)"
+Language=prs
+Label="fa (fa-olpc)"
+
+[keyboard-af-ps-olpc]
+Description="Keyboard - Dari - Pashto (Afghanistan, OLPC)"
+Language=ps
+Label="ps (ps-olpc)"
+
+[keyboard-af-uz-olpc]
+Description="Keyboard - Dari - Uzbek (Afghanistan, OLPC)"
+Language=uz
+Label="uz (uz-olpc)"
+
+[keyboard-hr]
+Description="Keyboard - Croatian"
+Language=hr
+Label=hr
+
+[keyboard-hr-alternatequotes]
+Description="Keyboard - Croatian - Croatian (with guillemets)"
+Language=hr
+Label="hr (alternatequotes)"
+
+[keyboard-hr-unicode]
+Description="Keyboard - Croatian - Croatian (with Croatian digraphs)"
+Language=hr
+Label="hr (unicode)"
+
+[keyboard-hr-unicodeus]
+Description="Keyboard - Croatian - Croatian (US, with Croatian digraphs)"
+Language=hr
+Label="hr (unicodeus)"
+
+[keyboard-hr-us]
+Description="Keyboard - Croatian - Croatian (US)"
+Language=hr
+Label="hr (us)"
+
+[keyboard-ma]
+Description="Keyboard - Arabic (Morocco)"
+Language=ary
+Label=ar
+
+[keyboard-ma-tifinagh]
+Description="Keyboard - Arabic (Morocco) - Berber (Morocco, Tifinagh)"
+Language=
+Label="ber (tifinagh)"
+
+[keyboard-ma-tifinagh-alt]
+Description="Keyboard - Arabic (Morocco) - Berber (Morocco, Tifinagh alt.)"
+Language=
+Label="ber (tifinagh-alt)"
+
+[keyboard-ma-tifinagh-alt-phonetic]
+Description="Keyboard - Arabic (Morocco) - Berber (Morocco, Tifinagh phonetic, alt.)"
+Language=
+Label="ber (tifinagh-alt-phonetic)"
+
+[keyboard-ma-tifinagh-extended]
+Description="Keyboard - Arabic (Morocco) - Berber (Morocco, Tifinagh extended)"
+Language=
+Label="ber (tifinagh-extended)"
+
+[keyboard-ma-tifinagh-phonetic]
+Description="Keyboard - Arabic (Morocco) - Berber (Morocco, Tifinagh phonetic)"
+Language=
+Label="ber (tifinagh-phonetic)"
+
+[keyboard-ma-tifinagh-extended-phonetic]
+Description="Keyboard - Arabic (Morocco) - Berber (Morocco, Tifinagh extended phonetic)"
+Language=
+Label="ber (tifinagh-extended-phonetic)"
+
+[keyboard-ma-french]
+Description="Keyboard - Arabic (Morocco) - French (Morocco)"
+Language=fr
+Label="fr (french)"
+
+[keyboard-ma-rif]
+Description="Keyboard - Arabic (Morocco) - Tarifit"
+Language=rif
+Label=rif
+
+[keyboard-sy]
+Description="Keyboard - Arabic (Syria)"
+Language=syr
+Label=ar
+
+[keyboard-sy-syc]
+Description="Keyboard - Arabic (Syria) - Syriac"
+Language=syr
+Label=syc
+
+[keyboard-sy-syc_phonetic]
+Description="Keyboard - Arabic (Syria) - Syriac (phonetic)"
+Language=syr
+Label="syc (syc_phonetic)"
+
+[keyboard-sy-ku]
+Description="Keyboard - Arabic (Syria) - Kurdish (Syria, Latin Q)"
+Language=ku
+Label=ku
+
+[keyboard-sy-ku_alt]
+Description="Keyboard - Arabic (Syria) - Kurdish (Syria, Latin Alt-Q)"
+Language=ku
+Label="ku (ku_alt)"
+
+[keyboard-sy-ku_f]
+Description="Keyboard - Arabic (Syria) - Kurdish (Syria, F)"
+Language=ku
+Label="ku (ku_f)"
+
+[keyboard-at]
+Description="Keyboard - German (Austria)"
+Language=de
+Label=de
+
+[keyboard-at-nodeadkeys]
+Description="Keyboard - German (Austria) - German (Austria, no dead keys)"
+Language=de
+Label="at (nodeadkeys)"
+
+[keyboard-at-mac]
+Description="Keyboard - German (Austria) - German (Austria, Macintosh)"
+Language=de
+Label="at (mac)"
+
+[keyboard-nz]
+Description="Keyboard - English (New Zealand)"
+Language=en
+Label=en
+
+[keyboard-nz-mao]
+Description="Keyboard - English (New Zealand) - Maori"
+Language=mi
+Label="mi (mao)"
+
+[keyboard-epo]
+Description="Keyboard - Esperanto"
+Language=eo
+Label=eo
+
+[keyboard-epo-legacy]
+Description="Keyboard - Esperanto - Esperanto (legacy)"
+Language=eo
+Label="epo (legacy)"
+
+[keyboard-eu]
+Description="Keyboard - EurKEY (US)"
+Language=ca
+Label=eu
+
+[keyboard-za]
+Description="Keyboard - English (South Africa)"
+Language=en
+Label=en
+
+[keyboard-fo]
+Description="Keyboard - Faroese"
+Language=fo
+Label=fo
+
+[keyboard-fo-nodeadkeys]
+Description="Keyboard - Faroese - Faroese (no dead keys)"
+Language=fo
+Label="fo (nodeadkeys)"
+
+[keyboard-gb]
+Description="Keyboard - English (UK)"
+Language=en
+Label=en
+
+[keyboard-gb-extd]
+Description="Keyboard - English (UK) - English (UK, extended, Windows)"
+Language=en
+Label="gb (extd)"
+
+[keyboard-gb-intl]
+Description="Keyboard - English (UK) - English (UK, intl., with dead keys)"
+Language=en
+Label="gb (intl)"
+
+[keyboard-gb-dvorak]
+Description="Keyboard - English (UK) - English (UK, Dvorak)"
+Language=en
+Label="gb (dvorak)"
+
+[keyboard-gb-dvorakukp]
+Description="Keyboard - English (UK) - English (UK, Dvorak, with UK punctuation)"
+Language=en
+Label="gb (dvorakukp)"
+
+[keyboard-gb-mac]
+Description="Keyboard - English (UK) - English (UK, Macintosh)"
+Language=en
+Label="gb (mac)"
+
+[keyboard-gb-mac_intl]
+Description="Keyboard - English (UK) - English (UK, Macintosh, intl.)"
+Language=en
+Label="gb (mac_intl)"
+
+[keyboard-gb-colemak]
+Description="Keyboard - English (UK) - English (UK, Colemak)"
+Language=en
+Label="gb (colemak)"
+
+[keyboard-gb-colemak_dh]
+Description="Keyboard - English (UK) - English (UK, Colemak-DH)"
+Language=en
+Label="gb (colemak_dh)"
+
+[keyboard-gb-gla]
+Description="Keyboard - English (UK) - Scottish Gaelic"
+Language=en
+Label="gd (gla)"
+
+[keyboard-gb-pl]
+Description="Keyboard - English (UK) - Polish (British keyboard)"
+Language=pl
+Label=pl
+
+[keyboard-gb-sun_type6]
+Description="Keyboard - English (UK) - English (UK, Sun Type 6/7)"
+Language=en
+Label="gb (sun_type6)"
+
+[keyboard-ke]
+Description="Keyboard - Swahili (Kenya)"
+Language=sw
+Label=sw
+
+[keyboard-ke-kik]
+Description="Keyboard - Swahili (Kenya) - Kikuyu"
+Language=ki
+Label="ki (kik)"
+
+[keyboard-md]
+Description="Keyboard - Moldavian"
+Language=ro
+Label=ro
+
+[keyboard-md-gag]
+Description="Keyboard - Moldavian - Gagauz (Moldova)"
+Language=gag
+Label=gag
+
+[keyboard-us]
+Description="Keyboard - English (US)"
+Language=en
+Label=en
+
+[keyboard-us-euro]
+Description="Keyboard - English (US) - English (US, euro on 5)"
+Language=en
+Label="us (euro)"
+
+[keyboard-us-intl]
+Description="Keyboard - English (US) - English (US, intl., with dead keys)"
+Language=en
+Label="us (intl)"
+
+[keyboard-us-alt-intl]
+Description="Keyboard - English (US) - English (US, alt. intl.)"
+Language=en
+Label="us (alt-intl)"
+
+[keyboard-us-altgr-intl]
+Description="Keyboard - English (US) - English (intl., with AltGr dead keys)"
+Language=en
+Label="us (altgr-intl)"
+
+[keyboard-us-mac]
+Description="Keyboard - English (US) - English (Macintosh)"
+Language=en
+Label="us (mac)"
+
+[keyboard-us-colemak]
+Description="Keyboard - English (US) - English (Colemak)"
+Language=en
+Label="us (colemak)"
+
+[keyboard-us-colemak_dh]
+Description="Keyboard - English (US) - English (Colemak-DH)"
+Language=en
+Label="us (colemak_dh)"
+
+[keyboard-us-colemak_dh_wide]
+Description="Keyboard - English (US) - English (Colemak-DH Wide)"
+Language=en
+Label="us (colemak_dh_wide)"
+
+[keyboard-us-colemak_dh_ortho]
+Description="Keyboard - English (US) - English (Colemak-DH Ortholinear)"
+Language=en
+Label="us (colemak_dh_ortho)"
+
+[keyboard-us-colemak_dh_iso]
+Description="Keyboard - English (US) - English (Colemak-DH ISO)"
+Language=en
+Label="us (colemak_dh_iso)"
+
+[keyboard-us-colemak_dh_wide_iso]
+Description="Keyboard - English (US) - English (Colemak-DH Wide ISO)"
+Language=en
+Label="us (colemak_dh_wide_iso)"
+
+[keyboard-us-dvorak]
+Description="Keyboard - English (US) - English (Dvorak)"
+Language=en
+Label="us (dvorak)"
+
+[keyboard-us-dvorak-intl]
+Description="Keyboard - English (US) - English (Dvorak, intl., with dead keys)"
+Language=en
+Label="us (dvorak-intl)"
+
+[keyboard-us-dvorak-alt-intl]
+Description="Keyboard - English (US) - English (Dvorak, alt. intl.)"
+Language=en
+Label="us (dvorak-alt-intl)"
+
+[keyboard-us-dvorak-l]
+Description="Keyboard - English (US) - English (Dvorak, left-handed)"
+Language=en
+Label="us (dvorak-l)"
+
+[keyboard-us-dvorak-r]
+Description="Keyboard - English (US) - English (Dvorak, right-handed)"
+Language=en
+Label="us (dvorak-r)"
+
+[keyboard-us-dvorak-classic]
+Description="Keyboard - English (US) - English (classic Dvorak)"
+Language=en
+Label="us (dvorak-classic)"
+
+[keyboard-us-dvp]
+Description="Keyboard - English (US) - English (programmer Dvorak)"
+Language=en
+Label="us (dvp)"
+
+[keyboard-us-dvorak-mac]
+Description="Keyboard - English (US) - English (Dvorak, Macintosh)"
+Language=en
+Label="us (dvorak-mac)"
+
+[keyboard-us-norman]
+Description="Keyboard - English (US) - English (Norman)"
+Language=en
+Label="us (norman)"
+
+[keyboard-us-symbolic]
+Description="Keyboard - English (US) - English (US, Symbolic)"
+Language=en
+Label="us (symbolic)"
+
+[keyboard-us-workman]
+Description="Keyboard - English (US) - English (Workman)"
+Language=en
+Label="us (workman)"
+
+[keyboard-us-workman-intl]
+Description="Keyboard - English (US) - English (Workman, intl., with dead keys)"
+Language=en
+Label="us (workman-intl)"
+
+[keyboard-us-olpc2]
+Description="Keyboard - English (US) - English (the divide/multiply toggle the layout)"
+Language=en
+Label="us (olpc2)"
+
+[keyboard-us-chr]
+Description="Keyboard - English (US) - Cherokee"
+Language=chr
+Label=chr
+
+[keyboard-us-haw]
+Description="Keyboard - English (US) - Hawaiian"
+Language=haw
+Label=haw
+
+[keyboard-us-rus]
+Description="Keyboard - English (US) - Russian (US, phonetic)"
+Language=ru
+Label="ru (rus)"
+
+[keyboard-us-hbs]
+Description="Keyboard - English (US) - Serbo-Croatian (US)"
+Language=en
+Label="us (hbs)"
+
+[keyboard-us-intl-unicode]
+Description="Keyboard - English (US) - English (US, intl., AltGr Unicode combining)"
+Language=en
+Label="us (intl-unicode)"
+
+[keyboard-us-alt-intl-unicode]
+Description="Keyboard - English (US) - English (US, intl., AltGr Unicode combining, alt.)"
+Language=en
+Label="us (alt-intl-unicode)"
+
+[keyboard-us-ats]
+Description="Keyboard - English (US) - Atsina"
+Language=en
+Label="us (ats)"
+
+[keyboard-us-crd]
+Description="Keyboard - English (US) - Coeur d'Alene Salish"
+Language=crd
+Label="us (crd)"
+
+[keyboard-us-cz_sk_de]
+Description="Keyboard - English (US) - Czech, Slovak and German (US)"
+Language=en
+Label="us (cz_sk_de)"
+
+[keyboard-us-cz_sk_pl_de_es_fi_sv]
+Description="Keyboard - English (US) - Czech, Slovak, Polish, Spanish, Finnish, Swedish and German (US)"
+Language=en
+Label="us (cz_sk_pl_de_es_fi_sv)"
+
+[keyboard-us-drix]
+Description="Keyboard - English (US) - English (Drix)"
+Language=en
+Label="us (drix)"
+
+[keyboard-us-de_se_fi]
+Description="Keyboard - English (US) - German, Swedish and Finnish (US)"
+Language=en
+Label="us (de_se_fi)"
+
+[keyboard-us-ibm238l]
+Description="Keyboard - English (US) - English (US, IBM Arabic 238_L)"
+Language=en
+Label="us (ibm238l)"
+
+[keyboard-us-sun_type6]
+Description="Keyboard - English (US) - English (US, Sun Type 6/7)"
+Language=en
+Label="us (sun_type6)"
+
+[keyboard-us-carpalx]
+Description="Keyboard - English (US) - English (Carpalx)"
+Language=en
+Label="us (carpalx)"
+
+[keyboard-us-carpalx-intl]
+Description="Keyboard - English (US) - English (Carpalx, intl., with dead keys)"
+Language=en
+Label="us (carpalx-intl)"
+
+[keyboard-us-carpalx-altgr-intl]
+Description="Keyboard - English (US) - English (Carpalx, intl., with AltGr dead keys)"
+Language=en
+Label="us (carpalx-altgr-intl)"
+
+[keyboard-us-carpalx-full]
+Description="Keyboard - English (US) - English (Carpalx, full optimization)"
+Language=en
+Label="us (carpalx-full)"
+
+[keyboard-us-carpalx-full-intl]
+Description="Keyboard - English (US) - English (Carpalx, full optimization, intl., with dead keys)"
+Language=en
+Label="us (carpalx-full-intl)"
+
+[keyboard-us-carpalx-full-altgr-intl]
+Description="Keyboard - English (US) - English (Carpalx, full optimization, intl., with AltGr dead keys)"
+Language=en
+Label="us (carpalx-full-altgr-intl)"
+
+[keyboard-us-3l]
+Description="Keyboard - English (US) - English (3l)"
+Language=en
+Label="us (3l)"
+
+[keyboard-us-3l-cros]
+Description="Keyboard - English (US) - English (3l, Chromebook)"
+Language=en
+Label="us (3l-cros)"
+
+[keyboard-us-3l-emacs]
+Description="Keyboard - English (US) - English (3l, emacs)"
+Language=en
+Label="us (3l-emacs)"
+
+[keyboard-us-workman-p]
+Description="Keyboard - English (US) - English (Workman-P)"
+Language=en
+Label=workman-p
+
+[keyboard-us-scn]
+Description="Keyboard - English (US) - Sicilian (US keyboard)"
+Language=en
+Label="us (scn)"
+
+[keyboard-us-altgr-weur]
+Description="Keyboard - English (US) - English (Western European AltGr dead keys)"
+Language=en
+Label="us (altgr-weur)"
+
+[keyboard-ge]
+Description="Keyboard - Georgian"
+Language=ka
+Label=ka
+
+[keyboard-ge-ergonomic]
+Description="Keyboard - Georgian - Georgian (ergonomic)"
+Language=ka
+Label="ge (ergonomic)"
+
+[keyboard-ge-mess]
+Description="Keyboard - Georgian - Georgian (MESS)"
+Language=ka
+Label="ge (mess)"
+
+[keyboard-ge-os]
+Description="Keyboard - Georgian - Ossetian (Georgia)"
+Language=os
+Label="ge (os)"
+
+[keyboard-ge-ru]
+Description="Keyboard - Georgian - Russian (Georgia)"
+Language=ru
+Label=ru
+
+[keyboard-es]
+Description="Keyboard - Spanish"
+Language=es
+Label=es
+
+[keyboard-es-nodeadkeys]
+Description="Keyboard - Spanish - Spanish (no dead keys)"
+Language=es
+Label="es (nodeadkeys)"
+
+[keyboard-es-deadtilde]
+Description="Keyboard - Spanish - Spanish (dead tilde)"
+Language=es
+Label="es (deadtilde)"
+
+[keyboard-es-winkeys]
+Description="Keyboard - Spanish - Spanish (Windows)"
+Language=es
+Label="es (winkeys)"
+
+[keyboard-es-dvorak]
+Description="Keyboard - Spanish - Spanish (Dvorak)"
+Language=es
+Label="es (dvorak)"
+
+[keyboard-es-ast]
+Description="Keyboard - Spanish - Asturian (Spain, with bottom-dot H and L)"
+Language=ast
+Label=ast
+
+[keyboard-es-cat]
+Description="Keyboard - Spanish - Catalan (Spain, with middle-dot L)"
+Language=ca
+Label="ca (cat)"
+
+[keyboard-es-sun_type6]
+Description="Keyboard - Spanish - Spanish (Sun Type 6/7)"
+Language=es
+Label="es (sun_type6)"
+
+[keyboard-ee]
+Description="Keyboard - Estonian"
+Language=et
+Label=et
+
+[keyboard-ee-nodeadkeys]
+Description="Keyboard - Estonian - Estonian (no dead keys)"
+Language=et
+Label="ee (nodeadkeys)"
+
+[keyboard-ee-dvorak]
+Description="Keyboard - Estonian - Estonian (Dvorak)"
+Language=et
+Label="ee (dvorak)"
+
+[keyboard-ee-us]
+Description="Keyboard - Estonian - Estonian (US)"
+Language=et
+Label="ee (us)"
+
+[keyboard-ee-sun_type6]
+Description="Keyboard - Estonian - Estonian (Sun Type 6/7)"
+Language=et
+Label="ee (sun_type6)"
+
+[keyboard-bd]
+Description="Keyboard - Bangla"
+Language=bn
+Label=bn
+
+[keyboard-bd-probhat]
+Description="Keyboard - Bangla - Bangla (Probhat)"
+Language=bn
+Label="bd (probhat)"
+
+[keyboard-ph]
+Description="Keyboard - Filipino"
+Language=fil
+Label=ph
+
+[keyboard-ph-qwerty-bay]
+Description="Keyboard - Filipino - Filipino (QWERTY, Baybayin)"
+Language=fil
+Label="ph (qwerty-bay)"
+
+[keyboard-ph-capewell-dvorak]
+Description="Keyboard - Filipino - Filipino (Capewell-Dvorak, Latin)"
+Language=fil
+Label="ph (capewell-dvorak)"
+
+[keyboard-ph-capewell-dvorak-bay]
+Description="Keyboard - Filipino - Filipino (Capewell-Dvorak, Baybayin)"
+Language=fil
+Label="ph (capewell-dvorak-bay)"
+
+[keyboard-ph-capewell-qwerf2k6]
+Description="Keyboard - Filipino - Filipino (Capewell-QWERF 2006, Latin)"
+Language=fil
+Label="ph (capewell-qwerf2k6)"
+
+[keyboard-ph-capewell-qwerf2k6-bay]
+Description="Keyboard - Filipino - Filipino (Capewell-QWERF 2006, Baybayin)"
+Language=fil
+Label="ph (capewell-qwerf2k6-bay)"
+
+[keyboard-ph-colemak]
+Description="Keyboard - Filipino - Filipino (Colemak, Latin)"
+Language=fil
+Label="ph (colemak)"
+
+[keyboard-ph-colemak-bay]
+Description="Keyboard - Filipino - Filipino (Colemak, Baybayin)"
+Language=fil
+Label="ph (colemak-bay)"
+
+[keyboard-ph-dvorak]
+Description="Keyboard - Filipino - Filipino (Dvorak, Latin)"
+Language=fil
+Label="ph (dvorak)"
+
+[keyboard-ph-dvorak-bay]
+Description="Keyboard - Filipino - Filipino (Dvorak, Baybayin)"
+Language=fil
+Label="ph (dvorak-bay)"
+
+[keyboard-uz]
+Description="Keyboard - Uzbek"
+Language=uz
+Label=uz
+
+[keyboard-uz-latin]
+Description="Keyboard - Uzbek - Uzbek (Latin)"
+Language=uz
+Label="uz (latin)"
+
+[keyboard-lt]
+Description="Keyboard - Lithuanian"
+Language=lt
+Label=lt
+
+[keyboard-lt-std]
+Description="Keyboard - Lithuanian - Lithuanian (standard)"
+Language=lt
+Label="lt (std)"
+
+[keyboard-lt-us]
+Description="Keyboard - Lithuanian - Lithuanian (US)"
+Language=lt
+Label="lt (us)"
+
+[keyboard-lt-ibm]
+Description="Keyboard - Lithuanian - Lithuanian (IBM)"
+Language=lt
+Label="lt (ibm)"
+
+[keyboard-lt-lekp]
+Description="Keyboard - Lithuanian - Lithuanian (LEKP)"
+Language=lt
+Label="lt (lekp)"
+
+[keyboard-lt-lekpa]
+Description="Keyboard - Lithuanian - Lithuanian (LEKPa)"
+Language=lt
+Label="lt (lekpa)"
+
+[keyboard-lt-ratise]
+Description="Keyboard - Lithuanian - Lithuanian (Ratise)"
+Language=lt
+Label="lt (ratise)"
+
+[keyboard-lt-sgs]
+Description="Keyboard - Lithuanian - Samogitian"
+Language=sgs
+Label="lt (sgs)"
+
+[keyboard-lt-us_dvorak]
+Description="Keyboard - Lithuanian - Lithuanian (Dvorak)"
+Language=lt
+Label="lt (us_dvorak)"
+
+[keyboard-lt-sun_type6]
+Description="Keyboard - Lithuanian - Lithuanian (Sun Type 6/7)"
+Language=lt
+Label="lt (sun_type6)"
+
+[keyboard-fi]
+Description="Keyboard - Finnish"
+Language=fi
+Label=fi
+
+[keyboard-fi-winkeys]
+Description="Keyboard - Finnish - Finnish (Windows)"
+Language=fi
+Label="fi (winkeys)"
+
+[keyboard-fi-classic]
+Description="Keyboard - Finnish - Finnish (classic)"
+Language=fi
+Label="fi (classic)"
+
+[keyboard-fi-nodeadkeys]
+Description="Keyboard - Finnish - Finnish (classic, no dead keys)"
+Language=fi
+Label="fi (nodeadkeys)"
+
+[keyboard-fi-mac]
+Description="Keyboard - Finnish - Finnish (Macintosh)"
+Language=fi
+Label="fi (mac)"
+
+[keyboard-fi-smi]
+Description="Keyboard - Finnish - Northern Saami (Finland)"
+Language=se
+Label="fi (smi)"
+
+[keyboard-fi-sun_type6]
+Description="Keyboard - Finnish - Finnish (Sun Type 6/7)"
+Language=fi
+Label="fi (sun_type6)"
+
+[keyboard-fi-das]
+Description="Keyboard - Finnish - Finnish (DAS)"
+Language=fi
+Label="fi (das)"
+
+[keyboard-fi-fidvorak]
+Description="Keyboard - Finnish - Finnish (Dvorak)"
+Language=fi
+Label="fi (fidvorak)"
+
+[keyboard-cn]
+Description="Keyboard - Chinese"
+Language=zh
+Label=zh
+
+[keyboard-cn-altgr-pinyin]
+Description="Keyboard - Chinese - Hanyu Pinyin Letters (with AltGr dead keys)"
+Language=zh
+Label="cn (altgr-pinyin)"
+
+[keyboard-cn-mon_trad]
+Description="Keyboard - Chinese - Mongolian (Bichig)"
+Language=mvf
+Label="cn (mon_trad)"
+
+[keyboard-cn-mon_trad_todo]
+Description="Keyboard - Chinese - Mongolian (Todo)"
+Language=mvf
+Label="cn (mon_trad_todo)"
+
+[keyboard-cn-mon_trad_xibe]
+Description="Keyboard - Chinese - Mongolian (Xibe)"
+Language=sjo
+Label="cn (mon_trad_xibe)"
+
+[keyboard-cn-mon_trad_manchu]
+Description="Keyboard - Chinese - Mongolian (Manchu)"
+Language=mnc
+Label="cn (mon_trad_manchu)"
+
+[keyboard-cn-mon_trad_galik]
+Description="Keyboard - Chinese - Mongolian (Galik)"
+Language=mvf
+Label="cn (mon_trad_galik)"
+
+[keyboard-cn-mon_todo_galik]
+Description="Keyboard - Chinese - Mongolian (Todo Galik)"
+Language=mvf
+Label="cn (mon_todo_galik)"
+
+[keyboard-cn-mon_manchu_galik]
+Description="Keyboard - Chinese - Mongolian (Manchu Galik)"
+Language=mnc
+Label="cn (mon_manchu_galik)"
+
+[keyboard-cn-tib]
+Description="Keyboard - Chinese - Tibetan"
+Language=bo
+Label="cn (tib)"
+
+[keyboard-cn-tib_asciinum]
+Description="Keyboard - Chinese - Tibetan (with ASCII numerals)"
+Language=bo
+Label="cn (tib_asciinum)"
+
+[keyboard-cn-ug]
+Description="Keyboard - Chinese - Uyghur"
+Language=ug
+Label=ug
+
+[keyboard-ca]
+Description="Keyboard - French (Canada)"
+Language=fr
+Label=fr
+
+[keyboard-ca-fr-dvorak]
+Description="Keyboard - French (Canada) - French (Canada, Dvorak)"
+Language=fr
+Label="fr (fr-dvorak)"
+
+[keyboard-ca-fr-legacy]
+Description="Keyboard - French (Canada) - French (Canada, legacy)"
+Language=fr
+Label="fr (fr-legacy)"
+
+[keyboard-ca-multix]
+Description="Keyboard - French (Canada) - Canadian (CSA)"
+Language=fr
+Label="ca (multix)"
+
+[keyboard-ca-eng]
+Description="Keyboard - French (Canada) - English (Canada)"
+Language=en
+Label="en (eng)"
+
+[keyboard-ca-ike]
+Description="Keyboard - French (Canada) - Inuktitut"
+Language=iu
+Label=ike
+
+[keyboard-ca-kut]
+Description="Keyboard - French (Canada) - Kutenai"
+Language=fr
+Label=kut
+
+[keyboard-ca-shs]
+Description="Keyboard - French (Canada) - Secwepemctsin"
+Language=fr
+Label=shs
+
+[keyboard-ca-sun_type6]
+Description="Keyboard - French (Canada) - Multilingual (Canada, Sun Type 6/7)"
+Language=fr
+Label="ca (sun_type6)"
+
+[keyboard-gh]
+Description="Keyboard - English (Ghana)"
+Language=en
+Label=en
+
+[keyboard-gh-generic]
+Description="Keyboard - English (Ghana) - English (Ghana, multilingual)"
+Language=en
+Label="gh (generic)"
+
+[keyboard-gh-gillbt]
+Description="Keyboard - English (Ghana) - English (Ghana, GILLBT)"
+Language=en
+Label="gh (gillbt)"
+
+[keyboard-gh-akan]
+Description="Keyboard - English (Ghana) - Akan"
+Language=ak
+Label="ak (akan)"
+
+[keyboard-gh-avn]
+Description="Keyboard - English (Ghana) - Avatime"
+Language=avn
+Label=avn
+
+[keyboard-gh-ewe]
+Description="Keyboard - English (Ghana) - Ewe"
+Language=ee
+Label="ee (ewe)"
+
+[keyboard-gh-fula]
+Description="Keyboard - English (Ghana) - Fula"
+Language=ff
+Label="ff (fula)"
+
+[keyboard-gh-ga]
+Description="Keyboard - English (Ghana) - Ga"
+Language=gaa
+Label="gaa (ga)"
+
+[keyboard-gh-hausa]
+Description="Keyboard - English (Ghana) - Hausa (Ghana)"
+Language=ha
+Label="ha (hausa)"
+
+[keyboard-fr]
+Description="Keyboard - French"
+Language=fr
+Label=fr
+
+[keyboard-fr-nodeadkeys]
+Description="Keyboard - French - French (no dead keys)"
+Language=fr
+Label="fr (nodeadkeys)"
+
+[keyboard-fr-oss]
+Description="Keyboard - French - French (alt.)"
+Language=fr
+Label="fr (oss)"
+
+[keyboard-fr-oss_nodeadkeys]
+Description="Keyboard - French - French (alt., no dead keys)"
+Language=fr
+Label="fr (oss_nodeadkeys)"
+
+[keyboard-fr-oss_latin9]
+Description="Keyboard - French - French (alt., Latin-9 only)"
+Language=fr
+Label="fr (oss_latin9)"
+
+[keyboard-fr-latin9]
+Description="Keyboard - French - French (legacy, alt.)"
+Language=fr
+Label="fr (latin9)"
+
+[keyboard-fr-latin9_nodeadkeys]
+Description="Keyboard - French - French (legacy, alt., no dead keys)"
+Language=fr
+Label="fr (latin9_nodeadkeys)"
+
+[keyboard-fr-azerty]
+Description="Keyboard - French - French (AZERTY)"
+Language=fr
+Label="fr (azerty)"
+
+[keyboard-fr-afnor]
+Description="Keyboard - French - French (AZERTY, AFNOR)"
+Language=fr
+Label="fr (afnor)"
+
+[keyboard-fr-bepo]
+Description="Keyboard - French - French (BEPO)"
+Language=fr
+Label="fr (bepo)"
+
+[keyboard-fr-bepo_latin9]
+Description="Keyboard - French - French (BEPO, Latin-9 only)"
+Language=fr
+Label="fr (bepo_latin9)"
+
+[keyboard-fr-bepo_afnor]
+Description="Keyboard - French - French (BEPO, AFNOR)"
+Language=fr
+Label="fr (bepo_afnor)"
+
+[keyboard-fr-dvorak]
+Description="Keyboard - French - French (Dvorak)"
+Language=fr
+Label="fr (dvorak)"
+
+[keyboard-fr-mac]
+Description="Keyboard - French - French (Macintosh)"
+Language=fr
+Label="fr (mac)"
+
+[keyboard-fr-us]
+Description="Keyboard - French - French (US)"
+Language=fr
+Label="fr (us)"
+
+[keyboard-fr-bre]
+Description="Keyboard - French - Breton (France)"
+Language=br
+Label="fr (bre)"
+
+[keyboard-fr-oci]
+Description="Keyboard - French - Occitan"
+Language=oc
+Label="fr (oci)"
+
+[keyboard-fr-geo]
+Description="Keyboard - French - Georgian (France, AZERTY Tskapo)"
+Language=ka
+Label="fr (geo)"
+
+[keyboard-fr-sun_type6]
+Description="Keyboard - French - French (Sun Type 6/7)"
+Language=fr
+Label="fr (sun_type6)"
+
+[keyboard-fr-us-alt]
+Description="Keyboard - French - French (US with dead keys, alt.)"
+Language=fr
+Label="fr (us-alt)"
+
+[keyboard-fr-us-azerty]
+Description="Keyboard - French - French (US, AZERTY)"
+Language=fr
+Label="fr (us-azerty)"
+
+[keyboard-eg]
+Description="Keyboard - Arabic (Egypt)"
+Language=ar
+Label=ar
+
+[keyboard-eg-cop]
+Description="Keyboard - Arabic (Egypt) - Coptic"
+Language=cop
+Label=cop
+
+[keyboard-cd]
+Description="Keyboard - French (Democratic Republic of the Congo)"
+Language=fr
+Label=fr
+
+[keyboard-tg]
+Description="Keyboard - French (Togo)"
+Language=fr
+Label=fr
+
+[keyboard-kz]
+Description="Keyboard - Kazakh"
+Language=kk
+Label=kk
+
+[keyboard-kz-kazrus]
+Description="Keyboard - Kazakh - Kazakh (with Russian)"
+Language=kk
+Label="kz (kazrus)"
+
+[keyboard-kz-ext]
+Description="Keyboard - Kazakh - Kazakh (extended)"
+Language=kk
+Label="kz (ext)"
+
+[keyboard-kz-latin]
+Description="Keyboard - Kazakh - Kazakh (Latin)"
+Language=kk
+Label="kz (latin)"
+
+[keyboard-kz-ruskaz]
+Description="Keyboard - Kazakh - Russian (Kazakhstan, with Kazakh)"
+Language=ru
+Label="ru (ruskaz)"
+
+[keyboard-ch]
+Description="Keyboard - German (Switzerland)"
+Language=de
+Label=de
+
+[keyboard-ch-de_nodeadkeys]
+Description="Keyboard - German (Switzerland) - German (Switzerland, no dead keys)"
+Language=de
+Label="de (de_nodeadkeys)"
+
+[keyboard-ch-de_mac]
+Description="Keyboard - German (Switzerland) - German (Switzerland, Macintosh)"
+Language=de
+Label="de (de_mac)"
+
+[keyboard-ch-legacy]
+Description="Keyboard - German (Switzerland) - German (Switzerland, legacy)"
+Language=de
+Label="ch (legacy)"
+
+[keyboard-ch-fr]
+Description="Keyboard - German (Switzerland) - French (Switzerland)"
+Language=fr
+Label=fr
+
+[keyboard-ch-fr_nodeadkeys]
+Description="Keyboard - German (Switzerland) - French (Switzerland, no dead keys)"
+Language=fr
+Label="fr (fr_nodeadkeys)"
+
+[keyboard-ch-fr_mac]
+Description="Keyboard - German (Switzerland) - French (Switzerland, Macintosh)"
+Language=fr
+Label="fr (fr_mac)"
+
+[keyboard-ch-sun_type6_de]
+Description="Keyboard - German (Switzerland) - German (Switzerland, Sun Type 6/7)"
+Language=de
+Label="ch (sun_type6_de)"
+
+[keyboard-ch-sun_type6_fr]
+Description="Keyboard - German (Switzerland) - French (Switzerland, Sun Type 6/7)"
+Language=de
+Label="ch (sun_type6_fr)"
+
+[keyboard-gr]
+Description="Keyboard - Greek"
+Language=el
+Label=gr
+
+[keyboard-gr-simple]
+Description="Keyboard - Greek - Greek (simple)"
+Language=el
+Label="gr (simple)"
+
+[keyboard-gr-nodeadkeys]
+Description="Keyboard - Greek - Greek (no dead keys)"
+Language=el
+Label="gr (nodeadkeys)"
+
+[keyboard-gr-polytonic]
+Description="Keyboard - Greek - Greek (polytonic)"
+Language=el
+Label="gr (polytonic)"
+
+[keyboard-gr-sun_type6]
+Description="Keyboard - Greek - Greek (Sun Type 6/7)"
+Language=el
+Label="gr (sun_type6)"
+
+[keyboard-gr-colemak]
+Description="Keyboard - Greek - Greek (Colemak)"
+Language=el
+Label="gr (colemak)"
+
+[keyboard-tr]
+Description="Keyboard - Turkish"
+Language=tr
+Label=tr
+
+[keyboard-tr-f]
+Description="Keyboard - Turkish - Turkish (F)"
+Language=tr
+Label="tr (f)"
+
+[keyboard-tr-e]
+Description="Keyboard - Turkish - Turkish (E)"
+Language=tr
+Label="tr (e)"
+
+[keyboard-tr-alt]
+Description="Keyboard - Turkish - Turkish (Alt-Q)"
+Language=tr
+Label="tr (alt)"
+
+[keyboard-tr-intl]
+Description="Keyboard - Turkish - Turkish (intl., with dead keys)"
+Language=tr
+Label="tr (intl)"
+
+[keyboard-tr-ku]
+Description="Keyboard - Turkish - Kurdish (Turkey, Latin Q)"
+Language=ku
+Label=ku
+
+[keyboard-tr-ku_f]
+Description="Keyboard - Turkish - Kurdish (Turkey, F)"
+Language=ku
+Label="ku (ku_f)"
+
+[keyboard-tr-ku_alt]
+Description="Keyboard - Turkish - Kurdish (Turkey, Latin Alt-Q)"
+Language=ku
+Label="ku (ku_alt)"
+
+[keyboard-tr-sun_type6]
+Description="Keyboard - Turkish - Turkish (Sun Type 6/7)"
+Language=tr
+Label="tr (sun_type6)"
+
+[keyboard-tr-us]
+Description="Keyboard - Turkish - Turkish (i and ı swapped)"
+Language=tr
+Label="tr (us)"
+
+[keyboard-tr-otk]
+Description="Keyboard - Turkish - Old Turkic"
+Language=tr
+Label="tr (otk)"
+
+[keyboard-tr-otkf]
+Description="Keyboard - Turkish - Old Turkic (F)"
+Language=tr
+Label="tr (otkf)"
+
+[keyboard-tr-ot]
+Description="Keyboard - Turkish - Ottoman (Q)"
+Language=tr
+Label="tr (ot)"
+
+[keyboard-tr-otf]
+Description="Keyboard - Turkish - Ottoman (F)"
+Language=tr
+Label="tr (otf)"
+
+[keyboard-il]
+Description="Keyboard - Hebrew"
+Language=he
+Label=he
+
+[keyboard-il-si2]
+Description="Keyboard - Hebrew - Hebrew (SI-1452-2)"
+Language=he
+Label="il (si2)"
+
+[keyboard-il-lyx]
+Description="Keyboard - Hebrew - Hebrew (lyx)"
+Language=he
+Label="il (lyx)"
+
+[keyboard-il-phonetic]
+Description="Keyboard - Hebrew - Hebrew (phonetic)"
+Language=he
+Label="il (phonetic)"
+
+[keyboard-il-biblical]
+Description="Keyboard - Hebrew - Hebrew (Biblical, Tiro)"
+Language=he
+Label="il (biblical)"
+
+[keyboard-il-biblicalSIL]
+Description="Keyboard - Hebrew - Hebrew (Biblical, SIL phonetic)"
+Language=he
+Label="il (biblicalSIL)"
+
+[keyboard-de]
+Description="Keyboard - German"
+Language=de
+Label=de
+
+[keyboard-de-deadacute]
+Description="Keyboard - German - German (dead acute)"
+Language=de
+Label="de (deadacute)"
+
+[keyboard-de-deadgraveacute]
+Description="Keyboard - German - German (dead grave acute)"
+Language=de
+Label="de (deadgraveacute)"
+
+[keyboard-de-deadtilde]
+Description="Keyboard - German - German (dead tilde)"
+Language=de
+Label="de (deadtilde)"
+
+[keyboard-de-nodeadkeys]
+Description="Keyboard - German - German (no dead keys)"
+Language=de
+Label="de (nodeadkeys)"
+
+[keyboard-de-e1]
+Description="Keyboard - German - German (E1)"
+Language=de
+Label="de (e1)"
+
+[keyboard-de-e2]
+Description="Keyboard - German - German (E2)"
+Language=de
+Label="de (e2)"
+
+[keyboard-de-T3]
+Description="Keyboard - German - German (T3)"
+Language=de
+Label="de (T3)"
+
+[keyboard-de-us]
+Description="Keyboard - German - German (US)"
+Language=de
+Label="de (us)"
+
+[keyboard-de-dvorak]
+Description="Keyboard - German - German (Dvorak)"
+Language=de
+Label="de (dvorak)"
+
+[keyboard-de-mac]
+Description="Keyboard - German - German (Macintosh)"
+Language=de
+Label="de (mac)"
+
+[keyboard-de-mac_nodeadkeys]
+Description="Keyboard - German - German (Macintosh, no dead keys)"
+Language=de
+Label="de (mac_nodeadkeys)"
+
+[keyboard-de-neo]
+Description="Keyboard - German - German (Neo 2)"
+Language=de
+Label="de (neo)"
+
+[keyboard-de-qwerty]
+Description="Keyboard - German - German (QWERTY)"
+Language=de
+Label="de (qwerty)"
+
+[keyboard-de-dsb]
+Description="Keyboard - German - Lower Sorbian"
+Language=dsb
+Label="de (dsb)"
+
+[keyboard-de-dsb_qwertz]
+Description="Keyboard - German - Lower Sorbian (QWERTZ)"
+Language=dsb
+Label="de (dsb_qwertz)"
+
+[keyboard-de-ro]
+Description="Keyboard - German - Romanian (Germany)"
+Language=ro
+Label="de (ro)"
+
+[keyboard-de-ro_nodeadkeys]
+Description="Keyboard - German - Romanian (Germany, no dead keys)"
+Language=ro
+Label="de (ro_nodeadkeys)"
+
+[keyboard-de-ru]
+Description="Keyboard - German - Russian (Germany, phonetic)"
+Language=ru
+Label=ru
+
+[keyboard-de-tr]
+Description="Keyboard - German - Turkish (Germany)"
+Language=tr
+Label="de (tr)"
+
+[keyboard-de-hu]
+Description="Keyboard - German - German (with Hungarian letters, no dead keys)"
+Language=de
+Label="de (hu)"
+
+[keyboard-de-pl]
+Description="Keyboard - German - Polish (Germany, no dead keys)"
+Language=de
+Label="de (pl)"
+
+[keyboard-de-sun_type6]
+Description="Keyboard - German - German (Sun Type 6/7)"
+Language=de
+Label="de (sun_type6)"
+
+[keyboard-de-adnw]
+Description="Keyboard - German - German (Aus der Neo-Welt)"
+Language=de
+Label="de (adnw)"
+
+[keyboard-de-koy]
+Description="Keyboard - German - German (KOY)"
+Language=de
+Label="de (koy)"
+
+[keyboard-de-bone]
+Description="Keyboard - German - German (Bone)"
+Language=de
+Label="de (bone)"
+
+[keyboard-de-bone_eszett_home]
+Description="Keyboard - German - German (Bone, eszett in the home row)"
+Language=de
+Label="de (bone_eszett_home)"
+
+[keyboard-de-neo_qwertz]
+Description="Keyboard - German - German (Neo, QWERTZ)"
+Language=de
+Label="de (neo_qwertz)"
+
+[keyboard-de-neo_qwerty]
+Description="Keyboard - German - German (Neo, QWERTY)"
+Language=de
+Label="de (neo_qwerty)"
+
+[keyboard-de-ru-recom]
+Description="Keyboard - German - Russian (Germany, recommended)"
+Language=ru
+Label="ru (ru-recom)"
+
+[keyboard-de-ru-translit]
+Description="Keyboard - German - Russian (Germany, transliteration)"
+Language=ru
+Label="ru (ru-translit)"
+
+[keyboard-id]
+Description="Keyboard - Indonesian (Latin)"
+Language=id
+Label=id
+
+[keyboard-id-melayu-phonetic]
+Description="Keyboard - Indonesian (Latin) - Indonesian (Arab Melayu, phonetic)"
+Language=id
+Label="id (melayu-phonetic)"
+
+[keyboard-id-melayu-phoneticx]
+Description="Keyboard - Indonesian (Latin) - Indonesian (Arab Melayu, extended phonetic)"
+Language=id
+Label="id (melayu-phoneticx)"
+
+[keyboard-id-pegon-phonetic]
+Description="Keyboard - Indonesian (Latin) - Indonesian (Arab Pegon, phonetic)"
+Language=id
+Label="id (pegon-phonetic)"
+
+[keyboard-id-javanese]
+Description="Keyboard - Indonesian (Latin) - Javanese"
+Language=jax
+Label="id (javanese)"
+
+[keyboard-sn]
+Description="Keyboard - Wolof"
+Language=wo
+Label=wo
+
+[keyboard-az]
+Description="Keyboard - Azerbaijani"
+Language=az
+Label=az
+
+[keyboard-az-cyrillic]
+Description="Keyboard - Azerbaijani - Azerbaijani (Cyrillic)"
+Language=az
+Label="az (cyrillic)"
+
+[keyboard-kh]
+Description="Keyboard - Khmer (Cambodia)"
+Language=km
+Label=km
+
+[keyboard-hu]
+Description="Keyboard - Hungarian"
+Language=hu
+Label=hu
+
+[keyboard-hu-standard]
+Description="Keyboard - Hungarian - Hungarian (standard)"
+Language=hu
+Label="hu (standard)"
+
+[keyboard-hu-nodeadkeys]
+Description="Keyboard - Hungarian - Hungarian (no dead keys)"
+Language=hu
+Label="hu (nodeadkeys)"
+
+[keyboard-hu-qwerty]
+Description="Keyboard - Hungarian - Hungarian (QWERTY)"
+Language=hu
+Label="hu (qwerty)"
+
+[keyboard-hu-101_qwertz_comma_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 101-key, comma, dead keys)"
+Language=hu
+Label="hu (101_qwertz_comma_dead)"
+
+[keyboard-hu-101_qwertz_comma_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 101-key, comma, no dead keys)"
+Language=hu
+Label="hu (101_qwertz_comma_nodead)"
+
+[keyboard-hu-101_qwertz_dot_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 101-key, dot, dead keys)"
+Language=hu
+Label="hu (101_qwertz_dot_dead)"
+
+[keyboard-hu-101_qwertz_dot_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 101-key, dot, no dead keys)"
+Language=hu
+Label="hu (101_qwertz_dot_nodead)"
+
+[keyboard-hu-101_qwerty_comma_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 101-key, comma, dead keys)"
+Language=hu
+Label="hu (101_qwerty_comma_dead)"
+
+[keyboard-hu-101_qwerty_comma_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 101-key, comma, no dead keys)"
+Language=hu
+Label="hu (101_qwerty_comma_nodead)"
+
+[keyboard-hu-101_qwerty_dot_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 101-key, dot, dead keys)"
+Language=hu
+Label="hu (101_qwerty_dot_dead)"
+
+[keyboard-hu-101_qwerty_dot_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 101-key, dot, no dead keys)"
+Language=hu
+Label="hu (101_qwerty_dot_nodead)"
+
+[keyboard-hu-102_qwertz_comma_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 102-key, comma, dead keys)"
+Language=hu
+Label="hu (102_qwertz_comma_dead)"
+
+[keyboard-hu-102_qwertz_comma_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 102-key, comma, no dead keys)"
+Language=hu
+Label="hu (102_qwertz_comma_nodead)"
+
+[keyboard-hu-102_qwertz_dot_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 102-key, dot, dead keys)"
+Language=hu
+Label="hu (102_qwertz_dot_dead)"
+
+[keyboard-hu-102_qwertz_dot_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTZ, 102-key, dot, no dead keys)"
+Language=hu
+Label="hu (102_qwertz_dot_nodead)"
+
+[keyboard-hu-102_qwerty_comma_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 102-key, comma, dead keys)"
+Language=hu
+Label="hu (102_qwerty_comma_dead)"
+
+[keyboard-hu-102_qwerty_comma_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 102-key, comma, no dead keys)"
+Language=hu
+Label="hu (102_qwerty_comma_nodead)"
+
+[keyboard-hu-102_qwerty_dot_dead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 102-key, dot, dead keys)"
+Language=hu
+Label="hu (102_qwerty_dot_dead)"
+
+[keyboard-hu-102_qwerty_dot_nodead]
+Description="Keyboard - Hungarian - Hungarian (QWERTY, 102-key, dot, no dead keys)"
+Language=hu
+Label="hu (102_qwerty_dot_nodead)"
+
+[keyboard-hu-oldhunlig]
+Description="Keyboard - Hungarian - Old Hungarian (for ligatures)"
+Language=hu
+Label="oldhun(lig) (oldhunlig)"
+
+[keyboard-hu-us]
+Description="Keyboard - Hungarian - Hungarian (US)"
+Language=hu
+Label=us
+
+[keyboard-ng]
+Description="Keyboard - English (Nigeria)"
+Language=en
+Label=en
+
+[keyboard-ng-hausa]
+Description="Keyboard - English (Nigeria) - Hausa (Nigeria)"
+Language=ha
+Label="ha (hausa)"
+
+[keyboard-ng-igbo]
+Description="Keyboard - English (Nigeria) - Igbo"
+Language=ig
+Label="ig (igbo)"
+
+[keyboard-ng-yoruba]
+Description="Keyboard - English (Nigeria) - Yoruba"
+Language=yo
+Label="yo (yoruba)"
+
+[keyboard-is]
+Description="Keyboard - Icelandic"
+Language=is
+Label=is
+
+[keyboard-is-mac_legacy]
+Description="Keyboard - Icelandic - Icelandic (Macintosh, legacy)"
+Language=is
+Label="is (mac_legacy)"
+
+[keyboard-is-mac]
+Description="Keyboard - Icelandic - Icelandic (Macintosh)"
+Language=is
+Label="is (mac)"
+
+[keyboard-is-dvorak]
+Description="Keyboard - Icelandic - Icelandic (Dvorak)"
+Language=is
+Label="is (dvorak)"
+
+[keyboard-in]
+Description="Keyboard - Indian"
+Language=hi
+Label=in
+
+[keyboard-in-asm-kagapa]
+Description="Keyboard - Indian - Assamese (KaGaPa, phonetic)"
+Language=as
+Label="as (asm-kagapa)"
+
+[keyboard-in-ben]
+Description="Keyboard - Indian - Bangla (India)"
+Language=bn
+Label="bn (ben)"
+
+[keyboard-in-ben_probhat]
+Description="Keyboard - Indian - Bangla (India, Probhat)"
+Language=bn
+Label="bn (ben_probhat)"
+
+[keyboard-in-ben_baishakhi]
+Description="Keyboard - Indian - Bangla (India, Baishakhi)"
+Language=bn
+Label="in (ben_baishakhi)"
+
+[keyboard-in-ben_bornona]
+Description="Keyboard - Indian - Bangla (India, Bornona)"
+Language=bn
+Label="in (ben_bornona)"
+
+[keyboard-in-ben-kagapa]
+Description="Keyboard - Indian - Bangla (India, KaGaPa, phonetic)"
+Language=bn
+Label="in (ben-kagapa)"
+
+[keyboard-in-ben_gitanjali]
+Description="Keyboard - Indian - Bangla (India, Gitanjali)"
+Language=bn
+Label="in (ben_gitanjali)"
+
+[keyboard-in-ben_inscript]
+Description="Keyboard - Indian - Bangla (India, Baishakhi InScript)"
+Language=bn
+Label="in (ben_inscript)"
+
+[keyboard-in-eng]
+Description="Keyboard - Indian - English (India, with rupee)"
+Language=en
+Label="en (eng)"
+
+[keyboard-in-guj]
+Description="Keyboard - Indian - Gujarati"
+Language=gu
+Label="gu (guj)"
+
+[keyboard-in-guj-kagapa]
+Description="Keyboard - Indian - Gujarati (KaGaPa, phonetic)"
+Language=gu
+Label="gu (guj-kagapa)"
+
+[keyboard-in-bolnagri]
+Description="Keyboard - Indian - Hindi (Bolnagri)"
+Language=hi
+Label="hi (bolnagri)"
+
+[keyboard-in-hin-wx]
+Description="Keyboard - Indian - Hindi (Wx)"
+Language=hi
+Label="hi (hin-wx)"
+
+[keyboard-in-hin-kagapa]
+Description="Keyboard - Indian - Hindi (KaGaPa, phonetic)"
+Language=hi
+Label="hi (hin-kagapa)"
+
+[keyboard-in-kan]
+Description="Keyboard - Indian - Kannada"
+Language=kn
+Label="kn (kan)"
+
+[keyboard-in-kan-kagapa]
+Description="Keyboard - Indian - Kannada (KaGaPa, phonetic)"
+Language=kn
+Label="kn (kan-kagapa)"
+
+[keyboard-in-mal]
+Description="Keyboard - Indian - Malayalam"
+Language=ml
+Label="ml (mal)"
+
+[keyboard-in-mal_lalitha]
+Description="Keyboard - Indian - Malayalam (Lalitha)"
+Language=ml
+Label="ml (mal_lalitha)"
+
+[keyboard-in-mal_enhanced]
+Description="Keyboard - Indian - Malayalam (enhanced InScript, with rupee)"
+Language=ml
+Label="ml (mal_enhanced)"
+
+[keyboard-in-mal_poorna]
+Description="Keyboard - Indian - Malayalam (Poorna, extended InScript)"
+Language=ml
+Label="ml (mal_poorna)"
+
+[keyboard-in-mni]
+Description="Keyboard - Indian - Manipuri (Meitei)"
+Language=mni
+Label="in (mni)"
+
+[keyboard-in-mar-kagapa]
+Description="Keyboard - Indian - Marathi (KaGaPa, phonetic)"
+Language=mr
+Label="mr (mar-kagapa)"
+
+[keyboard-in-marathi]
+Description="Keyboard - Indian - Marathi (enhanced InScript)"
+Language=mr
+Label="in (marathi)"
+
+[keyboard-in-ori]
+Description="Keyboard - Indian - Oriya"
+Language=or
+Label="or (ori)"
+
+[keyboard-in-ori-bolnagri]
+Description="Keyboard - Indian - Oriya (Bolnagri)"
+Language=or
+Label="or (ori-bolnagri)"
+
+[keyboard-in-ori-wx]
+Description="Keyboard - Indian - Oriya (Wx)"
+Language=or
+Label="or (ori-wx)"
+
+[keyboard-in-guru]
+Description="Keyboard - Indian - Punjabi (Gurmukhi)"
+Language=pa
+Label="pa (guru)"
+
+[keyboard-in-jhelum]
+Description="Keyboard - Indian - Punjabi (Gurmukhi Jhelum)"
+Language=pa
+Label="pa (jhelum)"
+
+[keyboard-in-san-kagapa]
+Description="Keyboard - Indian - Sanskrit (KaGaPa, phonetic)"
+Language=sa
+Label="sa (san-kagapa)"
+
+[keyboard-in-sat]
+Description="Keyboard - Indian - Santali (Ol Chiki)"
+Language=sat
+Label=sat
+
+[keyboard-in-tamilnet]
+Description="Keyboard - Indian - Tamil (TamilNet '99)"
+Language=ta
+Label="ta (tamilnet)"
+
+[keyboard-in-tamilnet_tamilnumbers]
+Description="Keyboard - Indian - Tamil (TamilNet '99 with Tamil numerals)"
+Language=ta
+Label="ta (tamilnet_tamilnumbers)"
+
+[keyboard-in-tamilnet_TAB]
+Description="Keyboard - Indian - Tamil (TamilNet '99, TAB encoding)"
+Language=ta
+Label="ta (tamilnet_TAB)"
+
+[keyboard-in-tamilnet_TSCII]
+Description="Keyboard - Indian - Tamil (TamilNet '99, TSCII encoding)"
+Language=ta
+Label="ta (tamilnet_TSCII)"
+
+[keyboard-in-tam]
+Description="Keyboard - Indian - Tamil (InScript, with Arabic numerals)"
+Language=ta
+Label="ta (tam)"
+
+[keyboard-in-tam_tamilnumbers]
+Description="Keyboard - Indian - Tamil (InScript, with Tamil numerals)"
+Language=ta
+Label="ta (tam_tamilnumbers)"
+
+[keyboard-in-tel]
+Description="Keyboard - Indian - Telugu"
+Language=te
+Label="te (tel)"
+
+[keyboard-in-tel-kagapa]
+Description="Keyboard - Indian - Telugu (KaGaPa, phonetic)"
+Language=te
+Label="te (tel-kagapa)"
+
+[keyboard-in-tel-sarala]
+Description="Keyboard - Indian - Telugu (Sarala)"
+Language=te
+Label="te (tel-sarala)"
+
+[keyboard-in-urd-phonetic]
+Description="Keyboard - Indian - Urdu (phonetic)"
+Language=ur
+Label="ur (urd-phonetic)"
+
+[keyboard-in-urd-phonetic3]
+Description="Keyboard - Indian - Urdu (alt. phonetic)"
+Language=ur
+Label="ur (urd-phonetic3)"
+
+[keyboard-in-urd-winkeys]
+Description="Keyboard - Indian - Urdu (Windows)"
+Language=ur
+Label="ur (urd-winkeys)"
+
+[keyboard-in-iipa]
+Description="Keyboard - Indian - Indic IPA"
+Language=en
+Label="in (iipa)"
+
+[keyboard-in-modi-kagapa]
+Description="Keyboard - Indian - Modi (KaGaPa, phonetic)"
+Language=mr
+Label="mr (modi-kagapa)"
+
+[keyboard-in-san-misc]
+Description="Keyboard - Indian - Sanskrit symbols"
+Language=sa
+Label="sas (san-misc)"
+
+[keyboard-in-urd-navees]
+Description="Keyboard - Indian - Urdu (Navees)"
+Language=ur
+Label="ur (urd-navees)"
+
+[keyboard-it]
+Description="Keyboard - Italian"
+Language=it
+Label=it
+
+[keyboard-it-nodeadkeys]
+Description="Keyboard - Italian - Italian (no dead keys)"
+Language=it
+Label="it (nodeadkeys)"
+
+[keyboard-it-winkeys]
+Description="Keyboard - Italian - Italian (Windows)"
+Language=it
+Label="it (winkeys)"
+
+[keyboard-it-mac]
+Description="Keyboard - Italian - Italian (Macintosh)"
+Language=it
+Label="it (mac)"
+
+[keyboard-it-us]
+Description="Keyboard - Italian - Italian (US)"
+Language=it
+Label="it (us)"
+
+[keyboard-it-ibm]
+Description="Keyboard - Italian - Italian (IBM 142)"
+Language=it
+Label="it (ibm)"
+
+[keyboard-it-fur]
+Description="Keyboard - Italian - Friulian (Italy)"
+Language=fur
+Label="it (fur)"
+
+[keyboard-it-scn]
+Description="Keyboard - Italian - Sicilian"
+Language=it
+Label="it (scn)"
+
+[keyboard-it-geo]
+Description="Keyboard - Italian - Georgian (Italy)"
+Language=ka
+Label="it (geo)"
+
+[keyboard-it-sun_type6]
+Description="Keyboard - Italian - Italian (Sun Type 6/7)"
+Language=it
+Label="it (sun_type6)"
+
+[keyboard-it-lld]
+Description="Keyboard - Italian - Ladin (Italian keyboard)"
+Language=it
+Label="it_lld (lld)"
+
+[keyboard-it-lldde]
+Description="Keyboard - Italian - Ladin (German keyboard)"
+Language=de
+Label="de_lld (lldde)"
+
+[keyboard-it-dvorak]
+Description="Keyboard - Italian - Italian (Dvorak)"
+Language=it
+Label="it (dvorak)"
+
+[keyboard-jp]
+Description="Keyboard - Japanese"
+Language=ja
+Label=ja
+
+[keyboard-jp-kana]
+Description="Keyboard - Japanese - Japanese (Kana)"
+Language=ja
+Label="jp (kana)"
+
+[keyboard-jp-kana86]
+Description="Keyboard - Japanese - Japanese (Kana 86)"
+Language=ja
+Label="jp (kana86)"
+
+[keyboard-jp-OADG109A]
+Description="Keyboard - Japanese - Japanese (OADG 109A)"
+Language=ja
+Label="jp (OADG109A)"
+
+[keyboard-jp-mac]
+Description="Keyboard - Japanese - Japanese (Macintosh)"
+Language=ja
+Label="jp (mac)"
+
+[keyboard-jp-dvorak]
+Description="Keyboard - Japanese - Japanese (Dvorak)"
+Language=ja
+Label="jp (dvorak)"
+
+[keyboard-jp-sun_type6]
+Description="Keyboard - Japanese - Japanese (Sun Type 6)"
+Language=ja
+Label="jp (sun_type6)"
+
+[keyboard-jp-sun_type7]
+Description="Keyboard - Japanese - Japanese (Sun Type 7, PC-compatible)"
+Language=ja
+Label="jp (sun_type7)"
+
+[keyboard-jp-sun_type7_suncompat]
+Description="Keyboard - Japanese - Japanese (Sun Type 7, Sun-compatible)"
+Language=ja
+Label="jp (sun_type7_suncompat)"
+
diff --git a/ar/.config/fcitx5/conf/clipboard.conf b/ar/.config/fcitx5/conf/clipboard.conf
new file mode 100644
index 0000000..6284d6f
--- /dev/null
+++ b/ar/.config/fcitx5/conf/clipboard.conf
@@ -0,0 +1,7 @@
+# Trigger Key
+TriggerKey=
+# Paste Primary
+PastePrimaryKey=
+# Number of entries
+Number of entries=5
+
diff --git a/ar/.config/fcitx5/conf/hangul.conf b/ar/.config/fcitx5/conf/hangul.conf
new file mode 100644
index 0000000..e4cad03
--- /dev/null
+++ b/ar/.config/fcitx5/conf/hangul.conf
@@ -0,0 +1,19 @@
+# Keyboard Layout
+Keyboard=Dubeolsik
+# Hanja Mode Toggle Key
+HanjaModeToggleKey=
+# Prev Page
+PrevPage=
+# Next Page
+NextPage=
+# Prev Candidate
+PrevCandidate=
+# Next Candidate
+NextCandidate=
+# Auto Reorder
+AutoReorder=True
+# Word Commit
+WordCommit=False
+# Hanja Mode
+HanjaMode=False
+
diff --git a/ar/.config/fcitx5/conf/keyboard.conf b/ar/.config/fcitx5/conf/keyboard.conf
new file mode 100644
index 0000000..4609af6
--- /dev/null
+++ b/ar/.config/fcitx5/conf/keyboard.conf
@@ -0,0 +1,27 @@
+# Page size
+PageSize=5
+# Enable emoji in hint
+EnableEmoji=True
+# Enable emoji in quickphrase
+EnableQuickPhraseEmoji=True
+# Choose key modifier
+Choose Modifier=Super
+# Enable hint by default
+EnableHintByDefault=False
+# Trigger hint mode
+Hint Trigger=
+# Trigger hint mode for one time
+One Time Hint Trigger=
+# Use new compose behavior
+UseNewComposeBehavior=True
+# Type special characters with long press
+EnableLongPress=False
+# Applications disabled for long press
+LongPressBlocklist=
+
+[PrevCandidate]
+0=Shift+Tab
+
+[NextCandidate]
+0=Tab
+
diff --git a/ar/.config/fcitx5/conf/notifications.conf b/ar/.config/fcitx5/conf/notifications.conf
new file mode 100644
index 0000000..6e7d1b9
--- /dev/null
+++ b/ar/.config/fcitx5/conf/notifications.conf
@@ -0,0 +1,3 @@
+# Hidden Notifications
+HiddenNotifications=
+
diff --git a/ar/.config/fcitx5/conf/quickphrase.conf b/ar/.config/fcitx5/conf/quickphrase.conf
new file mode 100644
index 0000000..efe2e3a
--- /dev/null
+++ b/ar/.config/fcitx5/conf/quickphrase.conf
@@ -0,0 +1,9 @@
+# Trigger Key
+TriggerKey=
+# Choose key modifier
+Choose Modifier=None
+# Enable Spell check
+Spell=True
+# Fallback Spell check language
+FallbackSpellLanguage=en
+
diff --git a/ar/.config/fcitx5/conf/waylandim.conf b/ar/.config/fcitx5/conf/waylandim.conf
new file mode 100644
index 0000000..4e609cf
--- /dev/null
+++ b/ar/.config/fcitx5/conf/waylandim.conf
@@ -0,0 +1,5 @@
+# Detect current running application (Need restart)
+DetectApplication=True
+# Forward key event instead of commiting text if it is not handled
+PreferKeyEvent=True
+
diff --git a/ar/.config/fcitx5/conf/xcb.conf b/ar/.config/fcitx5/conf/xcb.conf
new file mode 100644
index 0000000..9d36cfd
--- /dev/null
+++ b/ar/.config/fcitx5/conf/xcb.conf
@@ -0,0 +1,5 @@
+# Allow Overriding System XKB Settings
+Allow Overriding System XKB Settings=False
+# Always set layout to be only group layout
+AlwaysSetToGroupLayout=True
+
diff --git a/ar/.config/fcitx5/conf/xim.conf b/ar/.config/fcitx5/conf/xim.conf
new file mode 100644
index 0000000..07b569c
--- /dev/null
+++ b/ar/.config/fcitx5/conf/xim.conf
@@ -0,0 +1,3 @@
+# Use On The Spot Style (Needs restarting)
+UseOnTheSpot=True
+
diff --git a/ar/.config/fcitx5/config b/ar/.config/fcitx5/config
new file mode 100644
index 0000000..af94e89
--- /dev/null
+++ b/ar/.config/fcitx5/config
@@ -0,0 +1,79 @@
+[Hotkey]
+# Enumerate when press trigger key repeatedly
+EnumerateWithTriggerKeys=True
+# Enumerate Input Method Forward
+EnumerateForwardKeys=
+# Enumerate Input Method Backward
+EnumerateBackwardKeys=
+# Skip first input method while enumerating
+EnumerateSkipFirst=False
+# Toggle embedded preedit
+TogglePreedit=
+
+[Hotkey/TriggerKeys]
+0=Control+Super+space
+1=
+2=
+
+[Hotkey/AltTriggerKeys]
+0=
+
+[Hotkey/EnumerateGroupForwardKeys]
+0=
+
+[Hotkey/EnumerateGroupBackwardKeys]
+0=
+
+[Hotkey/ActivateKeys]
+0=
+
+[Hotkey/DeactivateKeys]
+0=
+
+[Hotkey/PrevPage]
+0=Up
+
+[Hotkey/NextPage]
+0=Down
+
+[Hotkey/PrevCandidate]
+0=Shift+Tab
+
+[Hotkey/NextCandidate]
+0=Tab
+
+[Behavior]
+# Active By Default
+ActiveByDefault=False
+# Reset state on Focus In
+resetStateWhenFocusIn=No
+# Share Input State
+ShareInputState=All
+# Show preedit in application
+PreeditEnabledByDefault=True
+# Show Input Method Information when switch input method
+ShowInputMethodInformation=True
+# Show Input Method Information when changing focus
+showInputMethodInformationWhenFocusIn=False
+# Show compact input method information
+CompactInputMethodInformation=True
+# Show first input method information
+ShowFirstInputMethodInformation=True
+# Default page size
+DefaultPageSize=5
+# Override Xkb Option
+OverrideXkbOption=False
+# Custom Xkb Option
+CustomXkbOption=
+# Force Enabled Addons
+EnabledAddons=
+# Force Disabled Addons
+DisabledAddons=
+# Preload input method to be used by default
+PreloadInputMethod=True
+# Allow input method in the password field
+AllowInputMethodForPassword=False
+# Show preedit text when typing password
+ShowPreeditForPassword=False
+# Interval of saving user data in minutes
+AutoSavePeriod=30
diff --git a/ar/.config/fcitx5/profile b/ar/.config/fcitx5/profile
new file mode 100644
index 0000000..7430ea5
--- /dev/null
+++ b/ar/.config/fcitx5/profile
@@ -0,0 +1,23 @@
+[Groups/0]
+# Group Name
+Name=Default
+# Layout
+Default Layout=us
+# Default Input Method
+DefaultIM=hangul
+
+[Groups/0/Items/0]
+# Name
+Name=keyboard-us
+# Layout
+Layout=
+
+[Groups/0/Items/1]
+# Name
+Name=hangul
+# Layout
+Layout=
+
+[GroupOrder]
+0=Default
+
diff --git a/ar/.config/firefox/chrome/userChrome.css b/ar/.config/firefox/chrome/userChrome.css
new file mode 100644
index 0000000..f3e762e
--- /dev/null
+++ b/ar/.config/firefox/chrome/userChrome.css
@@ -0,0 +1,106 @@
+/* Firefox Compact Mode
+ *
+ * Copyright (c) 2021 Danny Colin
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+:root {
+ /* Tabbar: reduce tab margin */
+ --tab-block-margin: 4px 3px !important;
+}
+
+/* Tab: Reduce height */
+.tabbrowser-tab {
+ min-height: 24px !important;
+}
+
+/* Tab: Ensure tab height doesn't augment when arrowscrollbox is visible */
+#tabbrowser-arrowscrollbox {
+ --tab-min-height: 31px !important;
+ max-height: var(--tab-min-height);
+}
+
+/* Tab: Attention icon */
+.tabbrowser-tab:is([image], [pinned])
+ > .tab-stack
+ > .tab-content[attention]:not([selected="true"]),
+.tabbrowser-tab
+ > .tab-stack
+ > .tab-content[pinned][titlechanged]:not([selected="true"]) {
+ background-position-x: left 2px !important;
+ background-position-y: bottom 12.5px !important;
+}
+
+/* URLBar: Fix vertical alignment */
+#urlbar[breakout="true"]:not([open="true"]) {
+ --urlbar-height: 20px !important;
+ --urlbar-toolbar-height: 24px !important;
+}
+
+/* URLBar: Fix URL address vertical aligment when megabar is open */
+#urlbar[breakout="true"][open="true"] {
+ --urlbar-toolbar-height: 30px !important;
+}
+
+/* URLBar: Reduce row items padding */
+.urlbarView-row-inner {
+ padding-inline: var(--urlbarView-item-inline-padding);
+ padding-block: 2px !important;
+}
+
+/* URLBar: Reduce and realign row bookmark icons */
+.urlbarView-type-icon {
+ width: 10px !important;
+ height: 10px !important;
+ margin-bottom: 0 !important;
+ margin-inline-start: 10px !important;
+}
+
+/* URLBar: Reduce "This time, serach with" padding */
+#urlbar .search-one-offs:not([hidden]) {
+ padding-block: 4px !important;
+}
+
+/* Searchbar: Ensure toolbar height doesn't augment when searchbar is visible */
+#urlbar-container,
+#search-container {
+ padding-block: 0 !important;
+}
+
+/* Searchbar: Make sure the min-height of the input is the same as the popup */
+#search-container {
+ min-width: 192px !important;
+}
+
+/* Toolbar: Reduce spacing */
+#urlbar-container {
+ --urlbar-container-height: 30px !important;
+ margin-top: 0 !important;
+}
+
+/* Reload Button: Fix vertical alignment */
+#reload-button {
+ margin-block-start: -2px !important;
+}
+
+/* AppMenu: Header */
+.panel-header {
+ padding: 4px 0 0 4px !important;
+}
+
+/* AppMenu: Header button */
+.panel-header > .subviewbutton-back {
+ padding: 4px !important;
+}
+
+/* Windows 10 context menu */
+@media (-moz-os-version: windows-win10) {
+ /* Context Menu: Reduce vertical space */
+ menupopup > menuitem,
+ menupopup > menu {
+ padding-block: 2px !important;
+ }
+}
diff --git a/ar/.config/firefox/enhanceforyoutube.json b/ar/.config/firefox/enhanceforyoutube.json
new file mode 100644
index 0000000..29ab567
--- /dev/null
+++ b/ar/.config/firefox/enhanceforyoutube.json
@@ -0,0 +1,109 @@
+{
+ "version": "2.0.124.2",
+ "settings": {
+ "blur": 0,
+ "brightness": 100,
+ "contrast": 100,
+ "grayscale": 0,
+ "huerotate": 0,
+ "invert": 0,
+ "saturate": 100,
+ "sepia": 0,
+ "applyvideofilters": false,
+ "backgroundcolor": "#000000",
+ "backgroundopacity": 85,
+ "blackbars": false,
+ "blockautoplay": true,
+ "blockhfrformats": false,
+ "blockwebmformats": false,
+ "boostvolume": false,
+ "cinemamode": false,
+ "cinemamodewideplayer": true,
+ "controlbar": {
+ "active": true,
+ "autohide": false,
+ "centered": true,
+ "position": "absolute"
+ },
+ "controls": [
+ "loop",
+ "reverse-playlist",
+ "volume-booster",
+ "cards-end-screens",
+ "cinema-mode",
+ "size",
+ "pop-up-player",
+ "speed",
+ "video-filters",
+ "screenshot",
+ "keyboard-shortcuts",
+ "options"
+ ],
+ "controlsvisible": false,
+ "controlspeed": true,
+ "controlspeedmousebutton": false,
+ "controlvolume": false,
+ "controlvolumemousebutton": false,
+ "convertshorts": false,
+ "customcolors": {
+ "--main-color": "#00adee",
+ "--main-background": "#111111",
+ "--second-background": "#181818",
+ "--hover-background": "#232323",
+ "--main-text": "#eff0f1",
+ "--dimmer-text": "#cccccc",
+ "--shadow": "#000000"
+ },
+ "customcssrules": "",
+ "customscript": "",
+ "customtheme": false,
+ "darktheme": true,
+ "date": 1708305387160,
+ "defaultvolume": false,
+ "disableautoplay": true,
+ "executescript": false,
+ "expanddescription": false,
+ "filter": "none",
+ "hidecardsendscreens": false,
+ "hidechat": false,
+ "hidecomments": false,
+ "hiderelated": false,
+ "hideshorts": false,
+ "ignoreplaylists": true,
+ "ignorepopupplayer": true,
+ "localecode": "en_US",
+ "localedir": "ltr",
+ "message": false,
+ "miniplayer": true,
+ "miniplayerposition": "_bottom-right",
+ "miniplayersize": "_400x225",
+ "newestcomments": false,
+ "overridespeeds": true,
+ "pauseforegroundtab": false,
+ "pausevideos": true,
+ "popuplayersize": "640x360",
+ "qualityembeds": "hd1080",
+ "qualityembedsfullscreen": "highres",
+ "qualityplaylists": "hd1080",
+ "qualityplaylistsfullscreen": "highres",
+ "qualityvideos": "hd1080",
+ "qualityvideosfullscreen": "highres",
+ "reload": false,
+ "reversemousewheeldirection": false,
+ "selectquality": false,
+ "selectqualityfullscreenoff": false,
+ "selectqualityfullscreenon": false,
+ "speed": 1.2,
+ "speedvariation": 0.1,
+ "stopvideos": false,
+ "theatermode": false,
+ "theme": "default-dark",
+ "themevariant": "youtube-deep-dark.css",
+ "update": 1708305387160,
+ "volume": 50,
+ "volumemultiplier": 5,
+ "volumevariation": 5,
+ "wideplayer": false,
+ "wideplayerviewport": false
+ }
+}
diff --git a/ar/.config/firefox/thesiah.js b/ar/.config/firefox/thesiah.js
new file mode 100644
index 0000000..52412d4
--- /dev/null
+++ b/ar/.config/firefox/thesiah.js
@@ -0,0 +1,79 @@
+// These are changes made on top of the Arkenfox JS file to tweak it as
+// desired. Any of these settings can be overridden by the user.
+
+/* 0102: set startup page [SETUP-CHROME]
+ * 0=blank, 1=home, 2=last visited page, 3=resume previous session
+ * [NOTE] Session Restore is cleared with history (2811), and not used in Private Browsing mode
+ * [SETTING] General>Startup>Restore previous session ***/
+user_pref("browser.startup.page", 1);
+/* 0103: set HOME+NEWWINDOW page
+ * about:home=Firefox Home (default, see 0105), custom URL, about:blank
+ * [SETTING] Home>New Windows and Tabs>Homepage and new windows ***/
+user_pref("browser.startup.homepage", "https://www.searx.thesiah.xyz");
+/* 1244: enable HTTPS-Only mode in all windows
+ * When the top-level is HTTPS, insecure subresources are also upgraded (silent fail)
+ * [SETTING] to add site exceptions: Padlock>HTTPS-Only mode>On (after "Continue to HTTP Site")
+ * [SETTING] Privacy & Security>HTTPS-Only Mode (and manage exceptions)
+ * [TEST] http://example.com [upgrade]
+ * [TEST] http://httpforever.com/ | http://http.rip [no upgrade] ***/
+user_pref("dom.security.https_only_mode", false); // [FF76+] Allow access to http (i.e. not https) sites:
+/** SANITIZE ON SHUTDOWN: RESPECTS "ALLOW" SITE EXCEPTIONS FF103+ | v2 migration is FF128+ ***/
+/* 2815: set "Cookies" and "Site Data" to clear on shutdown (if 2810 is true) [SETUP-CHROME]
+ * [NOTE] Exceptions: A "cookie" permission also controls "offlineApps" (see note below). For cross-domain logins,
+ * add exceptions for both sites e.g. https://www.youtube.com (site) + https://accounts.google.com (single sign on)
+ * [NOTE] "offlineApps": Offline Website Data: localStorage, service worker cache, QuotaManager (IndexedDB, asm-cache)
+ * [NOTE] "sessions": Active Logins (has no site exceptions): refers to HTTP Basic Authentication [1], not logins via cookies
+ * [WARNING] Be selective with what sites you "Allow", as they also disable partitioning (1767271)
+ * [SETTING] to add site exceptions: Ctrl+I>Permissions>Cookies>Allow (when on the website in question)
+ * [SETTING] to manage site exceptions: Options>Privacy & Security>Permissions>Settings
+ * [1] https://en.wikipedia.org/wiki/Basic_access_authentication ***/
+user_pref("privacy.clearOnShutdown.cookies", false); // Cookies
+user_pref("privacy.clearOnShutdown.offlineApps", false); // Site Data
+user_pref("privacy.clearOnShutdown.sessions", false); // Active Logins [DEFAULT: true]
+user_pref("privacy.clearOnShutdown_v2.cookiesAndStorage", false); // Cookies, Site Data, Active Logins [FF128+]
+/* 5010: disable location bar suggestion types
+ * [SETTING] Search>Address Bar>When using the address bar, suggest ***/
+user_pref("browser.urlbar.suggest.history", false);
+user_pref("browser.urlbar.suggest.topsites", false); // [FF78+]
+/* 5012: disable location bar autofill
+ * [1] https://support.mozilla.org/kb/address-bar-autocomplete-firefox#w_url-autocomplete ***/
+user_pref("browser.urlbar.autoFill", false);
+/* 5021: disable location bar using search
+ * Don't leak URL typos to a search engine, give an error message instead
+ * Examples: "secretplace,com", "secretplace/com", "secretplace com", "secret place.com"
+ * [NOTE] This does not affect explicit user action such as using search buttons in the
+ * dropdown, or using keyword search shortcuts you configure in options (e.g. "d" for DuckDuckGo) ***/
+user_pref("keyword.enabled", true); // Enable the addition of search keywords:
+/* 5510: control when to send a cross-origin referer
+ * 0=always (default), 1=only if base domains match, 2=only if hosts match
+ * [NOTE] Will cause breakage: older modems/routers and some sites e.g banks, vimeo, icloud, instagram ***/
+user_pref("network.http.referer.XOriginPolicy", 0); // This could otherwise cause some issues on bank logins and other annoying sites:
+// 7018: disable service worker Web Notifications [FF44+]
+user_pref("dom.webnotifications.serviceworker.enabled", false);
+/* 7019: disable Push Notifications [FF44+]
+ * [WHY] Website "push" requires subscription, and the API is required for CRLite (1224)
+ * [NOTE] To remove all subscriptions, reset "dom.push.userAgentID"
+ * [1] https://support.mozilla.org/kb/push-notifications-firefox ***/
+user_pref("dom.push.enabled", false);
+
+// Bookmarks visibility
+user_pref("browser.toolbars.bookmarks.visibility", "newtab");
+user_pref("browser.uidensity", 1);
+// Disable the pocket antifeature:
+user_pref("extensions.pocket.enabled", false);
+// Fullscreen notifications
+user_pref("full-screen-api.warning.timeout", false);
+// Disable Firefox sync and its menu entries
+user_pref("identity.fxaccounts.enabled", false);
+// Keep cookies until expiration or user deletion:
+user_pref("network.cookie.lifetimePolicy", 0);
+// Prefil forms:
+user_pref("signon.prefillForms", false);
+// Enable custom userChrome.js:
+user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);
+// Fix the issue where right mouse button instantly clicks
+user_pref("ui.context_menus.after_mouseup", true);
+// Alt key menu
+user_pref("ui.key.menuAccessKeyFocuses", false);
+// Alt key change
+user_pref("ui.key.menuAccessKey", -1);
diff --git a/ar/.config/firefox/vdh-settings.json b/ar/.config/firefox/vdh-settings.json
new file mode 100644
index 0000000..a159c22
--- /dev/null
+++ b/ar/.config/firefox/vdh-settings.json
@@ -0,0 +1,90 @@
+{
+ "blacklist": {},
+ "license": null,
+ "conversionRules": [],
+ "outputConfigs": {},
+ "weh-prefs": {
+ "networkProbe": true,
+ "titleMode": "right",
+ "iconActivation": "currenttab",
+ "iconBadge": "tasks",
+ "hitsGotoTab": true,
+ "default-action-0": "quickdownload",
+ "default-action-1": "openlocalfile",
+ "default-action-2": "abort",
+ "smartnamerFnameSpaces": "keep",
+ "smartnamerFnameMaxlen": 64,
+ "downloadControlledMax": 10,
+ "downloadStreamControlledMax": 10,
+ "autoPin": false,
+ "mediaExtensions": "flv|ram|mpg|mpeg|avi|rm|wmv|mov|asf|mp3|rar|movie|divx|rbs|mp4|mpeg4",
+ "dashHideM4s": true,
+ "mpegtsHideTs": true,
+ "orphanExpiration": 60,
+ "chunksEnabled": true,
+ "hlsEnabled": true,
+ "dashEnabled": true,
+ "dashOnAdp": "audio_video",
+ "hlsDownloadAsM2ts": false,
+ "networkFilterOut": "/frag\\\\([0-9]+\\\\)/|[&\\\\?]range=[0-9]+-[0-9]+|/silverlight/",
+ "mediaweightThreshold": 2097152,
+ "mediaweightMinSize": 8192,
+ "tbvwsEnabled": true,
+ "converterThreads": "auto",
+ "converterAggregTuneH264": false,
+ "notifyReady": true,
+ "noPrivateNotification": true,
+ "avplayEnabled": true,
+ "blacklistEnabled": true,
+ "chunksConcurrentDownloads": 10,
+ "chunksPrefetchCount": 10,
+ "downloadRetries": 10,
+ "downloadRetryDelay": 1000,
+ "mpegtsSaveRaw": false,
+ "mpegtsSaveRawStreams": false,
+ "mpegtsEndsOnSeenChunk": true,
+ "converterKeepTmpFiles": false,
+ "backgroundReduxLogger": false,
+ "dlconvLastOutput": "",
+ "qrMessageNotAgain": false,
+ "coappShellEnabled": false,
+ "downloadCount": 272,
+ "donateNotAgainExpire": 0,
+ "popupHeightLeftOver": 100,
+ "coappDownloads": "coapp",
+ "lastDownloadDirectory": "/home/si/Videos",
+ "fileDialogType": "tab",
+ "alertDialogType": "tab",
+ "monitorNetworkRequests": true,
+ "chunkedCoappManifestsRequests": false,
+ "chunkedCoappDataRequests": true,
+ "coappRestartDelay": 1000,
+ "rememberLastDir": true,
+ "coappIdleExit": 60000,
+ "dialogAutoClose": false,
+ "convertControlledMax": 10,
+ "checkCoappOnStartup": true,
+ "coappUseProxy": true,
+ "downloadCompleteDelay": 1000,
+ "contentRedirectEnabled": true,
+ "contextMenuEnabled": true,
+ "toolsMenuEnabled": false,
+ "medialinkExtensions": "jpg|jpeg|gif|png|mpg|mpeg|avi|rm|wmv|mov|flv|mp3|mp4",
+ "medialinkMaxHits": 50,
+ "medialinkMinFilesPerGroup": 6,
+ "medialinkMinImgSize": 80,
+ "medialinkAutoDetect": false,
+ "medialinkScanImages": true,
+ "medialinkScanLinks": true,
+ "bulkEnabled": true,
+ "tbvwsGrabDelay": 2000,
+ "forcedCoappVersion": "",
+ "lastHlsDownload": 0,
+ "galleryNaming": "type-index",
+ "hlsRememberPrevLiveChunks": false,
+ "hlsEndTimeout": 20,
+ "tbvwsExtractionMethod": "page_android_ios_tvep",
+ "hitUpdateFloodProtect": 100,
+ "use_native_filepicker": false
+ }
+}
diff --git a/ar/.config/firefox/vimium-options.json b/ar/.config/firefox/vimium-options.json
new file mode 100644
index 0000000..c8581e1
--- /dev/null
+++ b/ar/.config/firefox/vimium-options.json
@@ -0,0 +1,25 @@
+{
+ "waitForEnterForFilteredHints": false,
+ "hideHud": true,
+ "keyMappings": "unmapAll\nmap j scrollDown\nmap k scrollUp\nmap gg scrollToTop\nmap G scrollToBottom\nmap <c-d> scrollPageDown\nmap <c-u> scrollPageUp\nmap K scrollFullPageDown\nmap J scrollFullPageUp\nmap h scrollLeft\nmap l scrollRight\nmap <c-h> scrollToLeft\nmap <c-l> scrollToRight\nmap r reload\nmap y copyCurrentUrl\nmap p openCopiedUrlInCurrentTab\nmap P openCopiedUrlInNewTab\nmap gu goUp\nmap gU goToRoot\nmap i enterInsertMode\nmap v enterVisualMode\nmap V enterVisualLineMode\nmap gf passNextKey\nmap gi focusInput\nmap f LinkHints.activateMode\nmap F LinkHints.activateModeToOpenInNewTab\nmap tf LinkHints.activateModeToOpenInNewForegroundTab\nmap tF LinkHints.activateModeWithQueue\nmap <c-y> LinkHints.activateModeToDownloadLink\nmap <c-o> LinkHints.activateModeToOpenIncognito\nmap Y LinkHints.activateModeToCopyLinkUrl\nmap [[ goPrevious\nmap ]] goNext\nmap nf nextFrame\nmap nF mainFrame\nmap m Marks.activateCreateMode\nmap ' Marks.activateGotoMode\nmap s Vomnibar.activate\nmap S Vomnibar.activateInNewTab\nmap b Vomnibar.activateBookmarks\nmap B Vomnibar.activateBookmarksInNewTab\nmap gt Vomnibar.activateTabSelection\nmap ge Vomnibar.activateEditUrl\nmap gE Vomnibar.activateEditUrlInNewTab\nmap / enterFindMode\nmap n performFind\nmap N performBackwardsFind\nmap <c-[> goBack\nmap <c-]> goForward\nmap tn createTab\nmap H previousTab\nmap L nextTab\nmap <c-p> visitPreviousTab\nmap t0 firstTab\nmap t$ lastTab\nmap tt duplicateTab\nmap tp togglePinTab\nmap tm toggleMuteTab\nmap <c-w> removeTab\nmap u restoreTab\nmap ts moveTabToNewWindow\nmap xh closeTabsOnLeft\nmap xl closeTabsOnRight\nmap xo closeOtherTabs\nmap << moveTabLeft\nmap >> moveTabRight\nmap ? showHelp\nmap gs toggleViewSource",
+ "linkHintCharacters": "asdfghjklweiovn",
+ "linkHintNumbers": "abcdefghijklmnopqrstuvwxyz",
+ "regexFindMode": true,
+ "ignoreKeyboardLayout": true,
+ "scrollStepSize": 40,
+ "grabBackFocus": true,
+ "searchEngines": "# Wiki\nw: https://www.wikipedia.org/w/index.php?title=Special:Search&search=%s Wikipedia Search\n\n# Google\ng: https://www.google.com/search?q=%s Google Search\n\n# Google Map\ngm: https://www.google.com/maps?q=%s Google Maps\n\n# Google Image\ngi: https://www.google.com/search?tbm=isch&q=%s Google Images\n\n# Google Drive\ngd: https://drive.google.com/drive/search?q=%s Google Drive\n\n# Brave\nb: https://search.brave.com/search?q=%s Brave Search\n\n# Github\ngh: https://github.com/search?q=%s&ref=opensearch Github Search\n\n# Github Gist\ngg: https://gist.github.com/search?q=%s&ref=opensearch Github Gist\n\n# Github Issues\nghi: https://github.com/issues?utf8=✓&q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22++%s Github Issues\n\n# Youtube\ny: https://www.youtube.com/results?search_query=%s Youtube Search\n\n# Discord\nd: http://www.discogs.com/search?q=%s&type=all Discord Search\n\n# Amazon\na: https://www.amazon.com/s/?field-keywords=%s Amazon Search\n\n# Ebay\ne: http://www.ebay.com/sch/i.html?_from=R40&_trksid=p5197.m570.l1313&_nkw=%s&_sacat=See-All-Categories Ebay Search\n\n# Home\nhome: https://www.google.com/maps/dir/home/%s Google Home\n\n# Current Site in Google\nsite: javascript:location='https://www.google.com/search?num=100&q=site:'+escape(location.hostname)+'+%s' Google Location\n\n# Stack Overflow\nso: https://stackoverflow.com/search?q=%s StackOverflow Search\n\n# Google Translation\ngt: https://translate.google.com/?hl=ko&sl=auto&tl=ko&text=%s%0A&op=translate Google Translate\n\n# Arch\nar: https://wiki.archlinux.org/index.php?search=%s Arch Wiki\n\n# Parcels\nt: https://parcelsapp.com/en/tracking/%s Tracking Parcelshttps://www.coupang.com/np/search?component=&q=%ED%8E%98%EC%BD%94%EB%A6%AC%EB%85%B8+%EB%A1%9C%EB%A7%88%EB%85%B8&channel=user\n\n# Naver\nn: https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%s Naver Search\n\n# Coupang\nc: https://www.coupang.com/np/search?component=&q=%s Coupang Search",
+ "searchUrl": "https://www.searx.thesiah.xyz/?q=",
+ "settingsVersion": "2.1.2",
+ "userDefinedLinkHintCss": "/* Mocha palette */\n\n:root {\n --rosewater: #f5e0dc;\n --peach: #fab387;\n --green: #a6e3a1;\n --blue: #89b4fa;\n --lavender: #b4befe;\n --text: #cdd6f4;\n --surface2: #585b70;\n --surface0: #313244;\n --base: #1e1e2e;\n --mantle: #181825;\n}\n#vimiumHintMarkerContainer div.internalVimiumHintMarker, #vimiumHintMarkerContainer div.vimiumHintMarker {\n padding: 3px 4px;\n background: var(--peach);\n border: 1;\n border-color: var(--mantle);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n}\n\n#vimiumHintMarkerContainer div span {\n color: var(--mantle);\n text-shadow: none;\n}\n\n#vimiumHintMarkerContainer div > .matchingCharacter {\n opacity: 1.0;\n}\n\n#vimiumHintMarkerContainer div > .matchingCharacter ~ span {\n color: var(--surface2);\n}\n\n#vomnibar {\n background: var(--base);\n border: 2px solid var(--lavender);\n animation: show 200ms cubic-bezier(0, 0, 0.2, 1) forwards;\n max-height: calc(100vh - 70px);\n overflow: hidden;\n}\n\n@keyframes show {\n 0% {\n transform: translateY(50px);\n opacity: 0;\n }\n 100% {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n#vomnibar input {\n color: var(--text);\n background: var(--base);\n border: none;\n height: unset;\n padding: 16px 30px;\n}\n\n#vomnibar .vomnibarSearchArea {\n border: none;\n padding: unset;\n background: var(--base)\n}\n\n#vomnibar ul {\n padding: 0;\n margin: 0;\n background: var(--base);\n border-top: 1px solid var(--surface0);\n}\n\n#vomnibar li {\n padding: 10px;\n border-bottom: 1px solid var(--surface0)\n}\n\n#vomnibar li .vomnibarTopHalf,\n#vomnibar li .vomnibarBottomHalf {\n padding: 3px 0;\n}\n\n#vomnibar li .vomnibarSource {\n color: var(--peach);\n}\n\n#vomnibar li em,\n#vomnibar li .vomnibarTitle {\n color: var(--blue);\n font-weight: bold;\n}\n\n#vomnibar li .vomnibarUrl {\n color: var(--rosewater);\n}\n\n#vomnibar li .vomnibarMatch {\n color: var(--green);\n font-weight: bold;\n}\n\n#vomnibar li .vomnibarTitle .vomnibarMatch {\n color: var(--blue);\n}\n\n#vomnibar li.vomnibarSelected {\n background-color: var(--surface0);\n}\n\ndiv.vimiumHUD {\n background: var(--base);\n border: none;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n}\n\ndiv.vimiumHUD span#hud-find-input,\ndiv.vimiumHUD .vimiumHUDSearchAreaInner {\n color: var(--text)\n}\n\ndiv.vimiumHUD .hud-find {\n background-color: var(--base);\n border: none;\n}\n\ndiv.vimiumHUD .vimiumHUDSearchArea {\n background-color: var(--base);\n}",
+ "exclusionRules": [
+ {
+ "passKeys": "",
+ "pattern": "https?://hostingervps.com/*"
+ },
+ {
+ "passKeys": "",
+ "pattern": "https?://en.key-test.ru/*"
+ }
+ ]
+}
diff --git a/ar/.config/fontconfig/fonts.conf b/ar/.config/fontconfig/fonts.conf
new file mode 100644
index 0000000..46cb9c6
--- /dev/null
+++ b/ar/.config/fontconfig/fonts.conf
@@ -0,0 +1,70 @@
+<?xml version='1.0'?>
+<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+<fontconfig>
+ <alias>
+ <family>serif</family>
+ <prefer>
+ <family>Source Han Serif KR</family>
+ <family>DejaVu Serif</family>
+ <family>Droid Serif</family>
+ <family>Libertinus Serif</family>
+ <family>Joy Pixels</family>
+ <family>Noto Color Emoji</family>
+ <family>FontAwesome</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>sans-serif</family>
+ <prefer>
+ <family>Source Han Serif KR</family>
+ <family>DejaVu Serif</family>
+ <family>Droid Serif</family>
+ <family>Libertinus Serif</family>
+ <family>Joy Pixels</family>
+ <family>Noto Color Emoji</family>
+ <family>FontAwesome</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>sans</family>
+ <prefer>
+ <family>Source Han Sans KR</family>
+ <family>DejaVu Sans</family>
+ <family>Droid Sans</family>
+ <family>Libertinus Sans</family>
+ <family>Joy Pixels</family>
+ <family>Noto Color Emoji</family>
+ <family>FontAwesome</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>monospace</family>
+ <prefer>
+ <family>Noto Sans Mono</family>
+ <family>Noto Sans Mono CJK KR</family>
+ <family>Libertinus Mono</family>
+ <family>FontAwesome</family>
+ <family>Braille</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>nerdmono</family>
+ <prefer>
+ <family>D2CodingLigature Nerd Font Mono</family>
+ <family>Hack Nerd Font Mono</family>
+ <family>Libertinus Mono</family>
+ <family>FontAwesome</family>
+ <family>Braille</family>
+ </prefer>
+ </alias>
+ <alias>
+ <family>ko</family>
+ <prefer>
+ <family>Noto Sans CJK KR</family>
+ <family>D2CodingLigature Nerd Font</family>
+ <family>Libertinus</family>
+ <family>FontAwesome</family>
+ <family>Braille</family>
+ </prefer>
+ </alias>
+</fontconfig>
diff --git a/ar/.config/gem/gemrc b/ar/.config/gem/gemrc
new file mode 100644
index 0000000..07cc908
--- /dev/null
+++ b/ar/.config/gem/gemrc
@@ -0,0 +1,3 @@
+gem: --no-document --install-dir .local/share/gem --bindir .local/bin
+install: --no-document --env-shebang
+update: --no-document --env-shebang
diff --git a/ar/.config/git/attributes b/ar/.config/git/attributes
new file mode 100644
index 0000000..aa5beb9
--- /dev/null
+++ b/ar/.config/git/attributes
@@ -0,0 +1,98 @@
+# Common settings that generally should always be used with your language specific settings
+
+# Auto detect text files and perform LF normalization
+* text=auto
+
+#
+# The above will handle all files NOT found below
+#
+
+# Documents
+*.bibtex text diff=bibtex
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
+*.md text diff=markdown
+*.mdx text diff=markdown
+*.tex text diff=tex
+*.adoc text
+*.textile text
+*.mustache text
+*.csv text eol=lf
+*.tab text
+*.tsv text
+*.txt text
+*.sql text eol=lf
+*.epub diff=astextplain
+
+# Graphics
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.tif binary
+*.tiff binary
+*.ico binary
+# SVG treated as text by default.
+# *.svg text
+# If you want to treat it as binary,
+# use the following line instead.
+*.svg binary
+*.eps binary
+
+# Scripts
+*.bash text eol=lf
+*.fish text eol=lf
+*.ksh text eol=lf
+*.sh text eol=lf
+*.zsh text eol=lf
+# These are explicitly windows files and should use crlf
+# *.bat text eol=crlf
+# *.cmd text eol=crlf
+# *.ps1 text eol=crlf
+
+# Serialisation
+*.json text
+*.toml text
+*.xml text
+*.yaml text
+*.yml text
+
+# Archives
+*.7z binary
+*.bz binary
+*.bz2 binary
+*.bzip2 binary
+*.gz binary
+*.lz binary
+*.lzma binary
+*.rar binary
+*.tar binary
+*.taz binary
+*.tbz binary
+*.tbz2 binary
+*.tgz binary
+*.tlz binary
+*.txz binary
+*.xz binary
+*.Z binary
+*.zip binary
+*.zst binary
+
+# Text files where line endings should be preserved
+*.patch -text
+
+#
+# Exclude files from exporting
+#
+
+.gitattributes export-ignore
+.gitignore export-ignore
+.gitkeep export-ignore
diff --git a/ar/.config/git/config b/ar/.config/git/config
new file mode 100644
index 0000000..aba3da1
--- /dev/null
+++ b/ar/.config/git/config
@@ -0,0 +1,81 @@
+[advice]
+ detachedHead = false
+[core]
+ excludesFile = ~/.config/git/ignore
+ pager = delta
+[color "status"]
+ added = green
+ branch = bold magenta
+ changed = yellow
+ untracked = blue
+[credential]
+ helper = store
+[delta]
+ blame-code-style = syntax
+ blame-palette = "#161617" "#1b1b1d" "#2a2a2d" "#3e3e43"
+ dark = true
+ features = mellow-barbet
+ file-style = brightwhite
+ file-decoration-style = none
+ file-added-label = [+]
+ file-copied-label = [==]
+ file-modified-label = [*]
+ file-removed-label = [-]
+ file-renamed-label = [->]
+ hunk-header-decoration-style = "#3e3e43" box ul
+ line-numbers = true
+ line-numbers-minus-style = brightred
+ line-numbers-plus-style = brightgreen
+ line-numbers-left-style = "#3e3e43"
+ line-numbers-right-style = "#3e3e43"
+ line-numbers-zero-style = "#57575f"
+ merge-conflict-begin-symbol = ~
+ merge-conflict-end-symbol = ~
+ merge-conflict-ours-diff-header-style = yellow bold
+ merge-conflict-ours-diff-header-decoration-style = "#3e3e43" box
+ merge-conflict-theirs-diff-header-style = yellow bold
+ merge-conflict-theirs-diff-header-decoration-style = "#3e3e43" box
+ minus-style = brightred black
+ minus-emph-style = black red
+ navigate = true # use n and N to move between diff sections
+ plus-style = brightgreen black
+ plus-emph-style = black green
+ syntax-theme = base16
+ side-by-side = false
+ true-color = auto
+ whitespace-error-style = black bold
+ zero-style = syntax
+[diff]
+ colorMoved = default
+ tool = vscode
+[difftool "vscode"]
+ cmd = code --wait --diff $LOCAL $REMOTE
+[include]
+ path = ~/.config/git/themes.gitconfig
+[init]
+ defaultBranch = master
+[interactive]
+ diffFilter = delta --color-only
+[merge]
+ tool = vscode
+ conflictstyle = diff3
+[mergetool]
+ keepBackup = false
+[mergetool "vscode"]
+ cmd = code --wait $MERGED
+[pager]
+ branch = false
+ diff = delta
+ log = delta
+ reflog = delta
+ show = delta
+[pull]
+ rebase = true
+ twohead = ort
+[rerere]
+ enabled = true
+[submodule]
+ recurse = true
+[user]
+ name = TheSiahxyz
+ email = 164138827+TheSiahxyz@users.noreply.github.com
diff --git a/ar/.config/git/gitk b/ar/.config/git/gitk
new file mode 100644
index 0000000..7149c6b
--- /dev/null
+++ b/ar/.config/git/gitk
@@ -0,0 +1,64 @@
+set mainfont {sans 9}
+set textfont {monospace 9}
+set uifont {sans 9 bold}
+set tabstop 8
+set findmergefiles 0
+set maxgraphpct 50
+set maxwidth 16
+set cmitmode patch
+set wrapcomment none
+set autoselect 1
+set autosellen 40
+set showneartags 1
+set maxrefs 20
+set visiblerefs {"master"}
+set hideremotes 0
+set showlocalchanges 1
+set datetimeformat {%Y-%m-%d %H:%M:%S}
+set limitdiffs 1
+set uicolor grey85
+set want_ttk 1
+set bgcolor white
+set fgcolor black
+set uifgcolor black
+set uifgdisabledcolor #999
+set colors {"#00ff00" red blue magenta darkgrey brown orange}
+set diffcolors {"#c30000" "#009800" blue}
+set mergecolors {red blue "#00ff00" purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"}
+set markbgcolor #e0e0ff
+set diffcontext 3
+set selectbgcolor gray85
+set foundbgcolor yellow
+set currentsearchhitbgcolor orange
+set extdifftool meld
+set perfile_attrs 0
+set headbgcolor #00ff00
+set headfgcolor black
+set headoutlinecolor black
+set remotebgcolor #ffddaa
+set tagbgcolor yellow
+set tagfgcolor black
+set tagoutlinecolor black
+set reflinecolor black
+set filesepbgcolor #aaaaaa
+set filesepfgcolor black
+set linehoverbgcolor #ffff80
+set linehoverfgcolor black
+set linehoveroutlinecolor black
+set mainheadcirclecolor yellow
+set workingfilescirclecolor red
+set indexcirclecolor #00ff00
+set circlecolors {white blue gray blue blue}
+set linkfgcolor blue
+set circleoutlinecolor black
+set diffbgcolors {"#fff3f3" "#f0fff0"}
+set web_browser xdg-open
+set geometry(main) 755x1012+6+27
+set geometry(state) normal
+set geometry(topwidth) 755
+set geometry(topheight) 210
+set geometry(pwsash0) "256 1"
+set geometry(pwsash1) "385 1"
+set geometry(botwidth) 323
+set geometry(botheight) 797
+set permviews {}
diff --git a/ar/.config/git/ignore b/ar/.config/git/ignore
new file mode 100644
index 0000000..ff57064
--- /dev/null
+++ b/ar/.config/git/ignore
@@ -0,0 +1,112 @@
+# Api/Keys
+api.json
+credentials
+
+# Backup files
+*~
+*.swp
+
+# Bookmakrs
+.vim-bookmarks
+
+# Compiled files
+*.class
+*.com
+*.dll
+*.exe
+*.o
+*.out
+*.so
+
+# Compressed
+*.7z
+*.dmg
+*.gz
+*.iso
+*.rar
+*.tar
+*.zip
+
+# Cookie
+cookie
+
+# Database
+*.db
+*.sql
+*.sqlite
+
+# Env
+.direnv/
+.envrc
+.env
+.local.application.conf
+
+# Git
+*.orig
+
+# GTK
+gtkfilechooser.ini
+
+# History
+.zcompdump
+
+# IDE directories
+.idea/
+
+# Lock
+*lock*.json
+
+# Log
+*.log
+
+# Mac
+**/.DS_Store
+**/._.DS_Store
+.DS_Store
+._.DS_Store
+
+# Nvim
+*vim/*.json
+
+# OS
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Private
+*personal*
+
+# Shortcuts
+shortcutrc
+zshnameddirrc
+
+# Suckless
+dmenu/dmenu
+dmenu/stest
+dwm/dwm
+dwmblocks/dwmblocks
+slock/slock
+st/st
+
+# Tmux
+*/tmux/plugins/
+
+# Transmission
+*/transmission-daemon/*/
+*/transmission-daemon/bandwidth-groups.json
+*/transmission-daemon/stats.json
+dht.dat
+
+# TheSiahxyz
+thesiah.mom
+
+# Vim
+*/vim/*/
+
+# VS code
+.vscode/
+
+# thesiah.mom
+*/thesiah/thesiah.mom
diff --git a/ar/.config/gitmux/gitmux.conf b/ar/.config/gitmux/gitmux.conf
new file mode 100644
index 0000000..6c7780e
--- /dev/null
+++ b/ar/.config/gitmux/gitmux.conf
@@ -0,0 +1,86 @@
+tmux:
+ # The symbols section defines the symbols printed before specific elements
+ # of Git status displayed in tmux status string.
+ symbols:
+ # current branch name.
+ branch: " "
+ # Git SHA1 hash (in 'detached' state).
+ hashprefix: ":"
+ # 'ahead count' when local and remote branch diverged.
+ ahead: " "
+ # 'behind count' when local and remote branch diverged.
+ behind: " "
+ # count of files in the staging area.
+ staged: "● "
+ # count of files in conflicts.
+ conflict: " "
+ # count of modified files.
+ modified: "󰓎 "
+ # count of untracked files.
+ untracked: " "
+ # count of stash entries.
+ stashed: " "
+ # count of inserted lines (stats section).
+ insertions: "✚ "
+ # count of deleted lines (stats section).
+ deletions: "󰍴 "
+ # Shown when the working tree is clean.
+ clean: " "
+
+ # Styles are tmux format strings used to specify text colors and attributes
+ # of Git status elements. See the STYLES section of tmux man page.
+ # https://man7.org/linux/man-pages/man1/tmux.1.html#STYLES.
+ styles:
+ # Clear previous style.
+ clear: "#[fg=#{@thm_fg}]"
+ # Special tree state strings such as [rebase], [merge], etc.
+ state: "#[fg=#{@thm_red},bold]"
+ # Local branch name
+ branch: "#[fg=#{@thm_peach},bold]"
+ # Remote branch name
+ remote: "#[fg=#{@thm_overlay_1}]"
+ # 'divergence' counts
+ divergence: "#[fg=#{@thm_pink}]"
+ # 'staged' count
+ staged: "#[fg=#{@thm_green},bold]"
+ # 'conflicts' count
+ conflict: "#[fg=#{@thm_red},bold]"
+ # 'modified' count
+ modified: "#[fg=#{@thm_maroon},bold]"
+ # 'untracked' count
+ untracked: "#[fg=#{@thm_mauve},bold]"
+ # 'stash' count
+ stashed: "#[fg=#{@thm_blue},bold]"
+ # 'insertions' count
+ insertions: "#[fg=#{@thm_green}]"
+ # 'deletions' count
+ deletions: "#[fg=#{@thm_red}]"
+ # 'clean' symbol
+ clean: "#[fg=#{@thm_green},bold]"
+ # The layout section defines what components gitmux shows and the order in
+ # which they appear on tmux status bar.
+ #
+ # Allowed components:
+ # - branch: local branch name. Examples: `⎇ main`, `⎇ :345e7a0` or `[rebase]`
+ # - remote-branch: remote branch name, for example: `origin/main`.
+ # - divergence: divergence between local and remote branch, if any. Example: `↓·2↑·1`
+ # - remote: alias for `remote-branch` followed by `divergence`, for example: `origin/main ↓·2↑·1`
+ # - flags: symbols representing the working tree state, for example `✚ 1 ⚑ 1 … 2`
+ # - stats: insertions/deletions (lines), for example`Σ56 Δ21`
+ # - some string `foo`: any other character of string is directly shown, for example `foo` or `|`
+ layout: [branch, remote-branch, divergence, " - ", flags]
+
+ # Additional configuration options.
+ options:
+ # Maximum displayed length for local and remote branch names.
+ branch_max_len: 0
+ # Trim left, right or from the center of the branch (`right`, `left` or `center`).
+ branch_trim: right
+ # Character indicating whether and where a branch was truncated.
+ ellipsis: …
+ # Hides the clean flag
+ hide_clean: false
+ # Swaps order of behind & ahead upstream counts - "↓·1↑·1" -> "↑·1↓·1".
+ swap_divergence: false
+ # Add a space between behind & ahead upstream counts.
+ divergence_space: false
diff --git a/ar/.config/gtk-2.0/gtkrc-2.0 b/ar/.config/gtk-2.0/gtkrc-2.0
new file mode 100644
index 0000000..28a78df
--- /dev/null
+++ b/ar/.config/gtk-2.0/gtkrc-2.0
@@ -0,0 +1,21 @@
+# DO NOT EDIT! This file will be overwritten by LXAppearance.
+# Any customization should be done in ~/.gtkrc-2.0.mine instead.
+
+include "~/.gtkrc-2.0.mine"
+gtk-theme-name="Arc-Gruvbox"
+gtk-icon-theme-name="Adwaita"
+gtk-font-name="Sans 10"
+gtk-cursor-theme-name="Adwaita"
+gtk-cursor-theme-size=0
+gtk-toolbar-style=GTK_TOOLBAR_TEXT
+gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
+gtk-button-images=0
+gtk-menu-images=1
+gtk-enable-event-sounds=1
+gtk-enable-input-feedback-sounds=1
+gtk-xft-antialias=1
+gtk-xft-hinting=1
+gtk-xft-hintstyle="hintfull"
+gtk-xft-rgba="rgb"
+gtk-im-module="fcitx"
+gtk-recent-files-max-age=0
diff --git a/ar/.config/gtk-3.0/settings.ini b/ar/.config/gtk-3.0/settings.ini
new file mode 100644
index 0000000..230e3b6
--- /dev/null
+++ b/ar/.config/gtk-3.0/settings.ini
@@ -0,0 +1,20 @@
+[Settings]
+gtk-theme-name=Arc-Gruvbox
+gtk-icon-theme-name=Adwaita
+gtk-font-name=Sans 10
+gtk-cursor-theme-size=0
+gtk-toolbar-style=GTK_TOOLBAR_TEXT
+gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
+gtk-button-images=0
+gtk-menu-images=1
+gtk-enable-event-sounds=1
+gtk-enable-input-feedback-sounds=1
+gtk-xft-antialias=1
+gtk-xft-hinting=1
+gtk-xft-hintstyle=hintfull
+gtk-xft-rgba=rgb
+gtk-cursor-theme-name=Adwaita
+gtk-im-module=fcitx
+gtk-recent-files-enabled=0
+gtk-recent-files-limit=0
+gtk-recent-files-max-age=0
diff --git a/ar/.config/lazygit/config.yml b/ar/.config/lazygit/config.yml
new file mode 100644
index 0000000..2620fa6
--- /dev/null
+++ b/ar/.config/lazygit/config.yml
@@ -0,0 +1,15 @@
+customCommands:
+ - key: "<c-a>"
+ context: "global"
+ command: "gh repo view --branch {{.CheckedOutBranch.Name}} --web"
+ description: "Open current branch in browser"
+
+ - key: "<c-p>"
+ context: "global"
+ command: "gh pr view --web"
+ description: "Open current PR in browser"
+
+ - key: "H"
+ context: "remotes"
+ command: "git push {{.SelectedRemote.Name}} {{.CheckedOutBranch.Name}}"
+ description: "Push to home remote"
diff --git a/ar/.config/lf/cleaner b/ar/.config/lf/cleaner
new file mode 100755
index 0000000..29f96bd
--- /dev/null
+++ b/ar/.config/lf/cleaner
@@ -0,0 +1,4 @@
+#!/bin/sh
+if [ -n "$FIFO_UEBERZUG" ]; then
+ printf '{"action": "remove", "identifier": "PREVIEW"}\n' >"$FIFO_UEBERZUG"
+fi
diff --git a/ar/.config/lf/icons b/ar/.config/lf/icons
new file mode 100644
index 0000000..dd8dd68
--- /dev/null
+++ b/ar/.config/lf/icons
@@ -0,0 +1,305 @@
+ln 
+or 
+tw t
+ow 
+st t
+di 
+pi p
+so s
+bd b
+cd c
+su u
+sg g
+ex 
+fi 󰈔
+Vagrantfile 
+*.styl 
+*.sass 
+*.scss 
+*.htm 
+*.html 
+*.slim 
+*.haml 
+*.ejs 
+*.css 
+*.less 
+*.md 
+*.mdx 
+*.markdown 
+*.rmd 
+*.json 
+*.webmanifest 
+*.js 
+*.mjs 
+*.jsx 
+*.rb 
+*.gemspec 
+*.rake 
+*.php 
+*.py 
+*.pyc 
+*.pyo 
+*.pyd 
+*.coffee 
+*.mustache 
+*.hbs 
+*.conf 
+*.ini 
+*.yml 
+*.yaml 
+*.toml 
+*.bat 
+*.mk 
+*.jpg 
+*.jpeg 
+*.bmp 
+*.png 
+*.webp 
+*.ico 
+*.twig 
+*.cpp 
+*.c++ 
+*.cxx 
+*.cc 
+*.cp 
+*.c 
+*.cs 󰌛
+*.h 
+*.hh 
+*.hpp 
+*.hxx 
+*.hs 
+*.lhs 
+*.nix 
+*.lua 
+*.java 
+*.sh 
+*.fish 
+*.bash 
+*.zsh 
+*.ksh 
+*.csh 
+*.awk 
+*.ps1 
+*.ml λ
+*.mli λ
+*.diff 
+*.db 
+*.sql 
+*.dump 
+*.clj 
+*.cljc 
+*.cljs 
+*.edn 
+*.scala 
+*.go 
+*.dart 
+*.xul 
+*.sln 
+*.suo 
+*.pl 
+*.pm 
+*.t 
+*.rss 
+'*.f#' 
+*.fsscript 
+*.fsx 
+*.fs 
+*.fsi 
+*.rs 
+*.rlib 
+*.d 
+*.erl 
+*.hrl 
+*.ex 
+*.exs 
+*.eex 
+*.leex 
+*.heex 
+*.vim 
+*.ai 
+*.psd 
+*.psb 
+*.ts 
+*.tsx 
+*.jl 
+*.pp 
+*.vue 
+*.elm 
+*.swift 
+*.xcplayground 
+*.tex 󰙩
+*.r 󰟔
+*.rproj 󰗆
+*.sol 󰡪
+*.pem 
+*gruntfile.coffee 
+*gruntfile.js 
+*gruntfile.ls 
+*gulpfile.coffee 
+*gulpfile.js 
+*gulpfile.ls 
+*mix.lock 
+*dropbox 
+*.ds_store 
+*.gitconfig 
+*.gitignore 
+*.gitattributes 
+*.gitlab-ci.yml 
+*.bashrc 
+*.zshrc 
+*.zshenv 
+*.zprofile 
+*.vimrc 
+*.gvimrc 
+*_vimrc 
+*_gvimrc 
+*.bashprofile 
+*favicon.ico 
+*license 
+*node_modules 
+*react.jsx 
+*procfile 
+*dockerfile 
+*docker-compose.yml 
+*rakefile 
+*config.ru 
+*gemfile 
+*makefile 
+*cmakelists.txt 
+*robots.txt 󰚩
+*Gruntfile.coffee 
+*Gruntfile.js 
+*Gruntfile.ls 
+*Gulpfile.coffee 
+*Gulpfile.js 
+*Gulpfile.ls 
+*Dropbox 
+*.DS_Store 
+*LICENSE 
+*React.jsx 
+*Procfile 
+*Dockerfile 
+*Docker-compose.yml 
+*Rakefile 
+*Gemfile 
+*Makefile 
+*CMakeLists.txt 
+*jquery.min.js 
+*angular.min.js 
+*backbone.min.js 
+*require.min.js 
+*materialize.min.js 
+*materialize.min.css 
+*mootools.min.js 
+*vimrc 
+*.tar 
+*.tgz 
+*.arc 
+*.arj 
+*.taz 
+*.lha 
+*.lz4 
+*.lzh 
+*.lzma 
+*.tlz 
+*.txz 
+*.tzo 
+*.t7z 
+*.zip 
+*.z 
+*.dz 
+*.gz 
+*.lrz 
+*.lz 
+*.lzo 
+*.xz 
+*.zst 
+*.tzst 
+*.bz2 
+*.bz 
+*.tbz 
+*.tbz2 
+*.tz 
+*.deb 
+*.rpm 
+*.jar 
+*.war 
+*.ear 
+*.sar 
+*.rar 
+*.alz 
+*.ace 
+*.zoo 
+*.cpio 
+*.7z 
+*.rz 
+*.cab 
+*.wim 
+*.swm 
+*.dwm 
+*.esd 
+*.mjpg 
+*.mjpeg 
+*.gif 
+*.pbm 
+*.pgm 
+*.ppm 
+*.tga 
+*.xbm 
+*.xpm 
+*.tif 
+*.tiff 
+*.svg 
+*.svgz 
+*.mng 
+*.pcx 
+*.mov 
+*.mpg 
+*.mpeg 
+*.m2v 
+*.mkv 
+*.webm 
+*.ogm 
+*.mp4 
+*.m4v 
+*.mp4v 
+*.vob 
+*.qt 
+*.nuv 
+*.wmv 
+*.asf 
+*.rm 
+*.rmvb 
+*.flc 
+*.avi 
+*.fli 
+*.flv 
+*.gl 
+*.dl 
+*.xcf 
+*.xwd 
+*.yuv 
+*.cgm 
+*.emf 
+*.ogv 
+*.ogx 
+*.aac 
+*.au 
+*.flac 
+*.m4a 
+*.mid 
+*.midi 
+*.mka 
+*.mp3 
+*.mpc 
+*.ogg 
+*.ra 
+*.wav 
+*.oga 
+*.opus 
+*.spx 
+*.xspf 
+*.heic 
+*.pdf :
+*.muttrc 
diff --git a/ar/.config/lf/lfrc b/ar/.config/lf/lfrc
new file mode 100644
index 0000000..2015704
--- /dev/null
+++ b/ar/.config/lf/lfrc
@@ -0,0 +1,607 @@
+### --- Basic Settings --- ###
+set autoquit true
+set cleaner '~/.config/lf/cleaner'
+set dircache false
+set drawbox true
+set dupfilefmt '%f_%n'
+set hidden false
+set hiddenfiles ".*:*.aux:*.log:*.bbl:*.bcf:*.blg:*.run.xml"
+set history false
+set icons true
+set ifs "\n"
+set ignorecase true
+set incsearch true
+set incfilter true
+set mouse true
+set number true
+set period 1
+set previewer '~/.config/lf/scope'
+set ratios 1:2:3
+set relativenumber true
+set scrolloff 10
+set shellopts '-eu'
+set tempmarks '123456'
+set truncatepct 50
+
+
+### --- Custom Functions --- ###
+# Chmod
+cmd chmods ${{
+ options=$(printf "x\nu+x\na+x\n000\n644\n666\n755\n777")
+ choice=$(printf "%s" "$options" | fzf)
+ case "$choice" in
+ "x") mod="+x";;
+ "u+x") mod="u+x";;
+ "a+x") mod="a+x";;
+ "000") mod="000";;
+ "644") mod="644";;
+ "666") mod="666";;
+ "755") mod="755";;
+ "777") mod="777";;
+ esac
+ for file in $fx; do
+ chmod -R $mod "$file"
+ done
+ printf "Permissions set to $mod for selected files/directories."
+ lf -remote "send $id reload"
+}}
+
+# Compress
+cmd compress ${{
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ set -f
+ printf "%s\n\t" "$fx"
+ printf "compress?[y/N]"
+ read ans
+ [ $ans = "y" ] && {
+ printf "%s\n\t" "$fx"
+ printf "file name: "
+ read ans
+ mkdir $ans && {
+ cp -r $fx $ans
+ tar czf $ans.tar.gz $ans
+ rm -rf $ans
+ } || {
+ printf "%s\n\t" "$fx"
+ printf "Permissions needs.\n"
+ printf "Continue in root?[y/N]"
+ read root
+ [ $root = "y" ] && {
+ printf "Compressing $ans...\n"
+ sudo mkdir $ans
+ sudo cp -r $fx $ans
+ sudo tar czf $ans.tar.gz $ans
+ sudo rm -rf $ans
+ }
+ }
+ }
+}}
+
+# Copy
+cmd copyto ${{
+ set -f
+ dest=$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' "${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" | fzf --prompt 'Copy to where? ' | sed 's|~|$HOME|')
+ [ -z "$dest" ] && exit
+ destpath=$(eval printf '%s' \"$dest\")
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ echo "From:"
+ echo "$fx" | sed 's/^/ /'
+ printf "To:\n %s\n\n\tcopy?[y/N]" "$destpath"
+ read -r ans
+ [ "$ans" != "y" ] && exit
+ for x in $fx; do
+ cp -ivr "$x" "$destpath"
+ done &&
+ notify-send "📋 File(s) copied." "File(s) copied to $destpath."
+}}
+cmd yank-dirname $dirname -- "$f" | head -c-1 | xclip -i -selection clipboard
+cmd yank-path $printf '%s' "$fx" | xclip -i -selection clipboard
+cmd yank-basename $basename -a -- $fx | head -c-1 | xclip -i -selection clipboard
+cmd yank-basename-without-extension &basename -a -- $fx | cut -d. -f1 | head -c-1 | xclip -i -selection clipboard
+
+# Create
+cmd mkdir ${{ clear; tput cup $(($(tput lines)/3)); tput bold
+ printf "Directory Name: "
+ read ans
+ mkdir -p $ans
+}}
+cmd mkfile ${{
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ printf "File Name: "
+ read ans
+ $EDITOR $ans
+}}
+cmd link %{{
+ set -- $(cat ~/.local/share/lf/files)
+ mode="$1"
+ shift
+ if [ "$#" -lt 1 ]; then
+ lf -remote "send $id echo no files to link"
+ exit 0
+ fi
+ case "$mode" in
+ # symbolically copy mode is indicating a soft link
+ copy) ln -sr -t . -- "$@";;
+ # while a move mode is indicating a hard link
+ move) ln -t . -- "$@";;
+ esac
+ rm ~/.local/share/lf/files
+ lf -remote "send clear"
+}}
+
+# Cut
+cmd cut-add %{{
+ sed '1s/.*/move/' "$XDG_DATA_HOME/lf/files" > "$XDG_DATA_HOME/lf/files.tmp"
+ mv "$XDG_DATA_HOME/lf/files.tmp" "$XDG_DATA_HOME/lf/files"
+ echo "$fx" >> "$XDG_DATA_HOME/lf/files"
+ lf -remote "send $id unselect"
+ lf -remote "send $id sync"
+}}
+cmd cut-remove %{{
+ sed '1s/.*/move/' "$XDG_DATA_HOME/lf/files" > "$XDG_DATA_HOME/lf/files.tmp"
+ mv "$XDG_DATA_HOME/lf/files.tmp" "$XDG_DATA_HOME/lf/files"
+ echo "$fx" | while read -r file; do
+ sed "\|$file|d" "$XDG_DATA_HOME/lf/files" > "$XDG_DATA_HOME/lf/files.tmp"
+ mv "$XDG_DATA_HOME/lf/files.tmp" "$XDG_DATA_HOME/lf/files"
+ done
+ lf -remote "send $id unselect"
+ lf -remote "send $id sync"
+}}
+
+# Delete
+cmd delete ${{
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ set -f
+ printf "%s\n\t" "$fx"
+ printf "delete?[y/N]"
+ read ans
+ [ $ans = "y" ] && {
+ rm -rf -- $fx 2>/dev/null || {
+ printf "sudo delete?[y/N]"
+ read ans
+ [ $ans = "y" ] && sudo rm -rf -- $fx
+ }
+ }
+}}
+
+cmd trash ${{
+ files=$(printf "$fx" | tr '\n' ';')
+ while [ "$files" ]; do
+ file=${files%%;*}
+
+ trash-put "$(basename "$file")"
+ if [ "$files" = "$file" ]; then
+ files=''
+ else
+ files="${files#*;}"
+ fi
+ done
+}}
+
+cmd restore_trash ${{
+ trash-restore
+}}
+
+# Extract
+cmd extract ${{
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ set -f
+ printf "%s\n\t" "$fx"
+ printf "extract?[y/N]"
+ read ans
+ [ $ans = "y" ] && {
+ case $fx in
+ *.tar.bz|*.tar.bz2|*.tbz|*.tbz2) tar xjf $fx;;
+ *.tar.gz|*.tgz) tar xzf $fx ;;
+ *.tar.xz|*.txz) tar xf $fx ;;
+ *.bz2) bunzip2 $fx ;;
+ *.rar) unrar e $fx ;;
+ *.gz) gunzip $fx ;;
+ *.tar) tar xf $fx ;;
+ *.zip) unzip $fx ;;
+ *.Z) uncompress $fx ;;
+ *.7z) 7z x $fx ;;
+ esac
+ }
+}}
+
+# Git
+cmd on-cd &{{
+ zoxide add "$PWD"
+ bash -c '
+ # display git repository status in your prompt
+ source /usr/share/git/completion/git-prompt.sh
+ GIT_PS1_SHOWDIRTYSTATE=auto
+ GIT_PS1_SHOWSTASHSTATE=auto
+ GIT_PS1_SHOWUNTRACKEDFILES=auto
+ GIT_PS1_SHOWUPSTREAM=auto
+ git=$(__git_ps1 " (%s)")
+
+ fmt="\033[32;1m%u@%h\033[0m:\033[34;1m%d\033[0m\033[1m%f$git\033[0m"
+ lf -remote "send $id set promptfmt \"$fmt\""
+ '
+}}
+
+# iPython
+cmd create-ipynb ${{
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ printf "File Name: "
+ read ans
+ vipy $ans
+}}
+
+# Move
+cmd moveto ${{
+ dest=$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' "${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" | fzf --prompt 'Move to where? ' | sed 's|~|$HOME|')
+ [ -z "$dest" ] && exit
+ destpath=$(eval printf '%s' \"$dest\")
+ clear; tput cup $(($(tput lines)/3)); tput bold
+ echo "From:"
+ echo "$fx" | sed 's/^/ /'
+ printf "To:\n %s\n\n\tmove?[y/N]" "$destpath"
+ read -r ans
+ [ "$ans" != "y" ] && exit
+ for x in $fx; do
+ mv -iv "$x" "$destpath"
+ done &&
+ notify-send "🚚 File(s) moved." "File(s) moved to $destpath."
+}}
+
+# MPV
+cmd mpvdir ${{
+ if [ -n "$fx" ]; then
+ set -- $fx
+ setsid -f mpv --really-quiet -- "$@"
+ else
+ for file in $(printf '%s\n' *.mp4 *.mkv *.avi *.flv *.webm *.mov *.mpg *.3gp *.ts *.rmvb | sort); do
+ [ -e "$file" ] && set -- "$@" "$file"
+ done
+ [ -n "$1" ] && setsid -f mpv --really-quiet -- "$@"
+ fi
+}}
+
+# Open
+cmd open ${{
+ case $(file --mime-type "$(readlink -f $f)" -b) in
+ application/octet-stream)
+ case ${f##*.} in
+ doc|docx|xls|xlsx|odt|ppt|pptx) setsid -f libreoffice $fx >/dev/null 2>&1 ;;
+ ghw) setsid -f gtkwave $f >/dev/null 2>&1 ;;
+ ts) setsid -f mpv $f -quiet >/dev/null 2>&1 ;;
+ *) setsid -f zathura $fx >/dev/null 2>&1 ;;
+ esac
+ ;;
+ application/epub*|application/pdf|application/postscript|application/vnd.djvu|image/vnd.djvu) setsid -f zathura $fx >/dev/null 2>&1 ;;
+ application/pgp-encrypted) $EDITOR $fx ;;
+ application/vnd.oasis.opendocument.text|application/vnd.oasis.opendocument.spreadsheet|application/vnd.oasis.opendocument.spreadsheet-template|application/vnd.oasis.opendocument.presentation-template|application/vnd.oasis.opendocument.presentation|application/vnd.ms-powerpoint|application/vnd.oasis.opendocument.graphics|application/vnd.oasis.opendocument.graphics-template|application/vnd.oasis.opendocument.formula|application/vnd.oasis.opendocument.database) setsid -f firefox $fx >/dev/null 2>&1 ;;
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet) localc $fx ;;
+ application/x-hwp|application/vnd.openxmlformats-officedocument.presentationml.presentation|application/vnd.openxmlformats-officedocument.wordprocessingml.document) libreoffice $fx ;;
+ audio/*|video/x-ms-asf) mpv --audio-display=no $f ;;
+ image/x-xcf) setsid -f gimp $f >/dev/null 2>&1 ;;
+ image/svg+xml) display -- $f ;;
+ image/*) rotdir $f | grep -i "\.\(png\|jpg\|jpeg\|gif\|webp\|avif\|tif\|ico\)\(_large\)*$" |
+ setsid -f nsxiv -aiop 2>/dev/null | while read -r file; do
+ [ -z "$file" ] && continue
+ lf -remote "send select \"$file\""
+ lf -remote "send toggle"
+ done &
+ ;;
+ text/csv) sc-im $fx;;
+ text/xml) lynx $fx;;
+ text/troff) groff -mom $fx -Tpdf | zathura - ;;
+ text/*|application/json|inode/x-empty|application/x-subrip) $EDITOR $fx;;
+ video/*) setsid -f mpv $f -quiet >/dev/null 2>&1 ;;
+ *) for f in $fx; do setsid -f $OPENER $f >/dev/null 2>&1; done;;
+ esac
+}}
+
+# Rename
+cmd bulkrename ${{
+ tmpfile_old="$(mktemp)"
+ tmpfile_new="$(mktemp)"
+
+ [ -n "$fs" ] && fs=$(basename -a $fs) || fs=$(ls)
+
+ echo "$fs" > "$tmpfile_old"
+ echo "$fs" > "$tmpfile_new"
+ $EDITOR "$tmpfile_new"
+
+ [ "$(wc -l < "$tmpfile_old")" -eq "$(wc -l < "$tmpfile_new")" ] || { rm -f "$tmpfile_old" "$tmpfile_new"; exit 1; }
+
+ paste "$tmpfile_old" "$tmpfile_new" | while IFS="$(printf '\t')" read -r src dst
+ do
+ [ "$src" = "$dst" ] || [ -e "$dst" ] || mv -- "$src" "$dst"
+ done
+
+ rm -f "$tmpfile_old" "$tmpfile_new"
+ lf -remote "send $id unselect"
+}}
+
+# Samba
+cmd share-samba ${{
+ printf "%s\n\t" "$fx"
+ printf "Share this folder?[y/N]"
+ read ans
+ [ $ans = "y" ] && dmenusamba "$f"
+}}
+
+# Select
+cmd select-type &{{
+ set -f
+ [ "$#" -eq 0 ] && exit
+ files="$(
+ find "$PWD" -mindepth 1 -maxdepth 1 \
+ \( \( -type "$1" -o \( -type l -a -exec test -"$1" {} \; \) \) \) \
+ $([ "$lf_hidden" = false ] && printf '%s\n' -not -name '.*') -print0 |
+ sort -z |
+ sed -z 's/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g;s/^/"/;s/$/"/' |
+ tr '\0' ' ')"
+ [ -n "$files" ] && lf -remote "send $id :unselect; toggle $files"
+}}
+cmd select-dirs select-type d
+cmd select-files select-type f
+cmd select-videos &{{
+ set -f
+ files="$(
+ find "$PWD" -mindepth 1 -maxdepth 1 \( \( -type f -o \( -type l -a -exec test -f {} \; \) \) \) \( -iname '*.mp4' -o -iname '*.mkv' -o -iname '*.flv' -o -iname '*.avi' -o -iname '*.webm' -o -iname '*.mov' -o -iname '*.mpg' -o -iname '*.ts' -o -iname '*.wmv' -o -iname '*.vob' -o -iname '*.3gp' -o -iname '*.rmvb' \) \
+ $([ "$lf_hidden" = false ] && printf '%s\n' ! -name '.*') -print0 |
+ sort -z |
+ sed -z 's/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g;s/^/"/;s/$/"/' |
+ tr '\0' ' ')"
+ [ -n "$files" ] && lf -remote "send $id :unselect; toggle $files"
+}}
+cmd select-images &{{
+ set -f
+ files="$(
+ find "$PWD" -mindepth 1 -maxdepth 1 \( \( -type f -o \( -type l -a -exec test -f {} \; \) \) \) \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.tiff' -o -iname '*.tif' -o -iname '*.svg' -o -iname '*.webp' \) \
+ $([ "$lf_hidden" = false ] && printf '%s\n' ! -name '.*') -print0 |
+ sort -z |
+ sed -z 's/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g;s/^/"/;s/$/"/' |
+ tr '\0' ' ')"
+ [ -n "$files" ] && lf -remote "send $id :unselect; toggle $files"
+}}
+cmd select-music &{{
+ set -f
+ files="$(
+ find "$PWD" -mindepth 1 -maxdepth 1 \( \( -type f -o \( -type l -a -exec test -f {} \; \) \) \) \( -iname '*.mp3' -o -iname '*.flac' -o -iname '*.wav' -o -iname '*.aac' -o -iname '*.ogg' -o -iname '*.m4a' -o -iname '*.wma' \) \
+ $([ "$lf_hidden" = false ] && printf '%s\n' ! -name '.*') -print0 |
+ sort -z |
+ sed -z 's/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g;s/^/"/;s/$/"/' |
+ tr '\0' ' ')"
+ [ -n "$files" ] && lf -remote "send $id :unselect; toggle $files"
+}}
+cmd on-select &{{
+ lf -remote "send $id set statfmt \"$(eza -ld --color=always "$f")\""
+}}
+cmd load-select &{{
+ [ "$1" = "$id" ] && exit 0
+ lf -remote "send $id unselect"
+ if [ -s ~/.local/share/lf/select ]; then
+ files=$(tr '\n' '\0' < ~/.local/share/lf/select | xargs -0 printf ' %q')
+ lf -remote "send $id toggle $files"
+ fi
+}}
+cmd save-select &{{
+ printf "%s" "$fs" > ~/.local/share/lf/select
+ lf -remote "send load-select $id"
+}}
+cmd alt-paste &{{
+ if [ -n "$fs" ]; then
+ lf -remote "send $id :$1; save-select"
+ fi
+ lf -remote "send $id paste" || {
+ printf "%s\n\t" "$fx"
+ printf "Permissions needs.\n"
+ printf "Continue in root?[y/N]"
+ read root
+ [ $root = "y" ] && sudo lf -remote "send $id paste"
+ }
+ lf -remote "send clear"
+ lf -remote "send $id reload"
+}}
+
+# Traversal
+cmd fzf $nvim $(find . -name "$1" | fzf)
+cmd fzf_search ${{
+ RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
+ res="$(
+ FZF_DEFAULT_COMMAND="$RG_PREFIX ''" \
+ fzf --bind "change:reload:$RG_PREFIX {q} || true" \
+ --ansi --layout=reverse --header 'Search in files' \
+ | cut -d':' -f1 | sed 's/\\/\\\\/g;s/"/\\"/g'
+ )"
+ [ -n "$res" ] && lf -remote "send $id select \"$res\""
+}}
+cmd z %{{
+ result="$(zoxide query --exclude $PWD $@ | sed 's/\\/\\\\/g;s/"/\\"/g')"
+ lf -remote "send $id cd \"$result\""
+}}
+cmd zi ${{
+ result="$(zoxide query -i | sed 's/\\/\\\\/g;s/"/\\"/g')"
+ lf -remote "send $id cd \"$result\""
+}}
+cmd follow_link %{{
+ lf -remote "send ${id} select '$(readlink $f)'"
+}}
+cmd lastnvim ${{
+ list=$(nvim -u NONE --headless +'lua io.write(table.concat(vim.v.oldfiles, "\n") .. "\n")' +qa)
+ file=$(printf "%s" "$list" | while read -r file; do
+ [ -f "$file" ] && printf "%s\n" "$file"
+ done | fzf --reverse || lf -remote "send $id reload")
+ [ -n "$file" ] && $EDITOR "$file"
+}}
+cmd edit-config ${{
+ $EDITOR ~/.config/lf/lfrc
+ lf -remote "send $id source ~/.config/lf/lfrc"
+}}
+
+cmd wine-run ${{
+ if [ $(file --mime-type "$(readlink -f $f)" -b) = "application/vnd.microsoft.portable-executable" ]; then
+ file="${f##*/}"
+ file="${file%.*}"
+ export WINEPREFIX="${WINEPREFIX:-${XDG_DATA_HOME:-${HOME}/.local/share}/wine}/$file"
+ wine $f
+ fi
+}}
+
+# Wallpaper
+cmd setbg "$1"
+cmd setlock "$1"
+
+### --- Bindings --- ###
+# Unbinding
+map b
+map c
+map d
+map f
+map g
+map H
+map M
+map p
+map s
+map t
+map v
+map w
+map y
+map z
+map ,
+
+# Shortcuts
+source ~/.config/lf/shortcutrc
+
+# Chmod
+map zx chmods
+
+# Clear && Unselect
+map <c-l> :clear; unselect
+
+# Compress
+map C compress
+
+# Copy
+# map yy %cp -ri -- $fs .; clear
+map Y $printf "%s" "$fx" | xclip -selection clipboard; clear
+map yb yank-basename
+map yd yank-dirname
+map ye copyto; clear
+map yl $printf "%s" "$fx" | sed -E 's/^.+\[/https:\/\/piped.video\/watch?v=/' | sed -E 's/\]\..+//' | xclip -selection clipboard
+map yn yank-basename-without-extension
+map yp yank-path
+map yt $printf "%s" "$fx" | sed -E 's/^.+\[/https:\/\/www.youtube.com\/watch?v=/' | sed -E 's/\]\..+//' | xclip -selection clipboard
+map yy copy
+
+# Create
+map Md mkdir
+map Mf mkfile
+map Ml link
+
+# Cut
+map dd cut-add
+map dr cut-remove
+
+# Delete
+map D trash; clear
+map <delete> delete; clear
+
+# Editor
+map <enter> $$EDITOR "$f"
+map <c-v> push :!nvim<space>
+map vlf edit-config
+map vll lastnvim
+map vln $$EDITOR "$(nvim -u NONE --headless +'lua io.write(vim.v.oldfiles[1] .. "\n")' +qa)"
+
+# Extract
+map E extract; clear
+
+# iPython
+map Mi create-ipynb
+
+# Move
+map Mt moveto; clear
+
+# MPV
+map Mpv mpvdir; unselect
+
+# Nsxiv
+map th $nsxiv -apt "$(pwd)"
+
+# Open
+map O $mimeopen "$f"
+map o open
+map <c-o> $mimeopen --ask "$f"
+
+# Paste
+map PP alt-paste cut
+map pp alt-paste copy
+
+# Preview
+map zd set dirpreviews!
+map zp set preview!
+
+# Program
+map T $setsid -f $TERMINAL >/dev/null 2>&1
+map x $$f
+map X !$f
+
+# Quit
+map <esc> quit
+map <c-c> quit
+
+# Rename
+map a :rename; cmd-right # after extension
+map A :rename; cmd-end # at the very end
+map B bulkrename
+map r :rename; cmd-delete-home # filename
+map R :rename; cmd-end; cmd-delete-home # entire filename
+map i :rename # before extension
+map I :rename; cmd-home # at the very beginning
+
+# Redraw && Reload
+map <c-r> :redraw; reload
+
+# Restore
+map U restore_trash
+
+# Samba
+map SMB share-samba
+
+# Select
+map <space> :toggle; down; save-select
+map sb :invert-below; save-select
+map sd select-dirs
+map sf select-files
+map si :invert; save-select
+map sm select-music
+map sp select-images
+map su :unselect; save-select
+map sU :glob-unselect; save-select
+map sv select-videos
+load-select
+
+# Sort
+map sa :set sortby atime; set info atime
+map sc :set sortby ctime; set info ctime
+map se :set sortby ext; set info
+map sn :set sortby natural; set info
+map st :set sortby time; set info time
+map sz :set sortby size; set info size
+
+# Tag
+map tg tag-toggle
+
+# Traversal
+map fa $lf -remote "send $id select \"$(fzf)\""
+map fb $lf -remote "send $id cd $(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs | fzf)"
+map fD zi
+map fd $lf -remote "send $id select \"$(find . -type d | fzf)\""
+map ff $lf -remote "send $id select \"$(find . -type f | fzf)\""
+map gl follow_link
+map <c-f> fzf_search
+map <c-z> z
+
+# Wallpaper
+map bg $setbg "$f"
+map bl $setlock "$f"
+
+# Wine
+map wi wine-run
diff --git a/ar/.config/lf/rooticons b/ar/.config/lf/rooticons
new file mode 100644
index 0000000..949d2f1
--- /dev/null
+++ b/ar/.config/lf/rooticons
@@ -0,0 +1,336 @@
+di 📁
+fi 📃
+tw 🤝
+ow 📂
+ln 🔗
+or ❌
+ex 🎯
+st t
+pi p
+so s
+bd b
+cd c
+su u
+sg g
+*.txt ✍
+*.mom ✍
+*.me ✍
+*.ms ✍
+*.avif 🖼
+*.png 🖼
+*.webp 🖼
+*.ico 🖼
+*.jpg 📸
+*.jpe 📸
+*.jpeg 📸
+*.gif 🖼
+*.svg 🗺
+*.tif 🖼
+*.tiff 🖼
+*.xcf 🖌
+*.html 🌎
+*.xml 📰
+*.gpg 🔒
+*.css 🎨
+*.pdf 📚
+*.djvu 📚
+*.epub 📚
+*.csv 📓
+*.xlsx 📓
+*.tex 📜
+*.md 📘
+*.r 📊
+*.R 📊
+*.rmd 📊
+*.Rmd 📊
+*.m 📊
+*.mp3 🎵
+*.opus 🎵
+*.ogg 🎵
+*.m4a 🎵
+*.flac 🎼
+*.wav 🎼
+*.mkv 🎥
+*.mp4 🎥
+*.webm 🎥
+*.mpeg 🎥
+*.avi 🎥
+*.mov 🎥
+*.mpg 🎥
+*.wmv 🎥
+*.m4b 🎥
+*.flv 🎥
+*.zip 📦
+*.rar 📦
+*.7z 📦
+*.tar 📦
+*.z64 🎮
+*.v64 🎮
+*.n64 🎮
+*.gba 🎮
+*.nes 🎮
+*.gdi 🎮
+*.1 ℹ
+*.nfo ℹ
+*.info ℹ
+*.log 📙
+*.iso 📀
+*.img 📀
+*.bib 🎓
+*.ged 👪
+*.part 💔
+*.torrent 🔽
+*.jar ♨
+*.java ♨
+*.styl 
+*.sass 
+*.scss 
+*.htm 
+*.slim 
+*.haml 
+*.ejs 
+*.less 
+*.mdx 
+*.markdown 
+*.json 
+*.webmanifest 
+*.js 
+*.mjs 
+*.jsx 
+*.rb 
+*.gemspec 
+*.rake 
+*.php 
+*.py 
+*.pyc 
+*.pyo 
+*.pyd 
+*.coffee 
+*.mustache 
+*.hbs 
+*.conf 
+*.ini 
+*.yml 
+*.yaml 
+*.toml 
+*.bat 
+*.mk 
+*.bmp 
+*.twig 
+*.cpp 
+*.c++ 
+*.cxx 
+*.cc 
+*.cp 
+*.c 
+*.cs 󰌛
+*.h 
+*.hh 
+*.hpp 
+*.hxx 
+*.hs 
+*.lhs 
+*.nix 
+*.lua 
+*.sh 
+*.fish 
+*.bash 
+*.zsh 
+*.ksh 
+*.csh 
+*.awk 
+*.ps1 
+*.ml λ
+*.mli λ
+*.diff 
+*.db 
+*.sql 
+*.dump 
+*.clj 
+*.cljc 
+*.cljs 
+*.edn 
+*.scala 
+*.go 
+*.dart 
+*.xul 
+*.sln 
+*.suo 
+*.pl 
+*.pm 
+*.t 
+*.rss 
+'*.f#' 
+*.fsscript 
+*.fsx 
+*.fs 
+*.fsi 
+*.rs 
+*.rlib 
+*.d 
+*.erl 
+*.hrl 
+*.ex 
+*.exs 
+*.eex 
+*.leex 
+*.heex 
+*.vim 
+*.ai 
+*.psd 
+*.psb 
+*.ts 
+*.tsx 
+*.jl 
+*.pp 
+*.vue 
+*.elm 
+*.swift 
+*.xcplayground 
+*.rproj 󰗆
+*.sol 󰡪
+*.pem 
+*gruntfile.coffee 
+*gruntfile.js 
+*gruntfile.ls 
+*gulpfile.coffee 
+*gulpfile.js 
+*gulpfile.ls 
+*mix.lock 
+*dropbox 
+*.ds_store 
+*.gitconfig 
+*.gitignore 
+*.gitattributes 
+*.gitlab-ci.yml 
+*.bashrc 
+*.zshrc 
+*.zshenv 
+*.zprofile 
+*.vimrc 
+*.gvimrc 
+*_vimrc 
+*_gvimrc 
+*.bashprofile 
+*favicon.ico 
+*license 
+*node_modules 
+*react.jsx 
+*procfile 
+*dockerfile 
+*docker-compose.yml 
+*rakefile 
+*config.ru 
+*gemfile 
+*makefile 
+*cmakelists.txt 
+*robots.txt 󰚩
+*Gruntfile.coffee 
+*Gruntfile.js 
+*Gruntfile.ls 
+*Gulpfile.coffee 
+*Gulpfile.js 
+*Gulpfile.ls 
+*Dropbox 
+*.DS_Store 
+*LICENSE 
+*React.jsx 
+*Procfile 
+*Dockerfile 
+*Docker-compose.yml 
+*Rakefile 
+*Gemfile 
+*Makefile 
+*CMakeLists.txt 
+*jquery.min.js 
+*angular.min.js 
+*backbone.min.js 
+*require.min.js 
+*materialize.min.js 
+*materialize.min.css 
+*mootools.min.js 
+*vimrc 
+Vagrantfile 
+*.tgz 
+*.arc 
+*.arj 
+*.taz 
+*.lha 
+*.lz4 
+*.lzh 
+*.lzma 
+*.tlz 
+*.txz 
+*.tzo 
+*.t7z 
+*.z 
+*.dz 
+*.gz 
+*.lrz 
+*.lz 
+*.lzo 
+*.xz 
+*.zst 
+*.tzst 
+*.bz2 
+*.bz 
+*.tbz 
+*.tbz2 
+*.tz 
+*.deb 
+*.rpm 
+*.war 
+*.ear 
+*.sar 
+*.alz 
+*.ace 
+*.zoo 
+*.cpio 
+*.rz 
+*.cab 
+*.wim 
+*.swm 
+*.dwm 
+*.esd 
+*.mjpg 
+*.mjpeg 
+*.bmp 
+*.pbm 
+*.pgm 
+*.ppm 
+*.tga 
+*.xbm 
+*.xpm 
+*.svgz 
+*.mng 
+*.pcx 
+*.m2v 
+*.ogm 
+*.m4v 
+*.mp4v 
+*.vob 
+*.qt 
+*.nuv 
+*.asf 
+*.rm 
+*.rmvb 
+*.flc 
+*.fli 
+*.gl 
+*.dl 
+*.xwd 
+*.yuv 
+*.cgm 
+*.emf 
+*.ogv 
+*.ogx 
+*.aac 
+*.au 
+*.mid 
+*.midi 
+*.mka 
+*.mpc 
+*.ra 
+*.oga 
+*.spx 
+*.xspf 
diff --git a/ar/.config/lf/scope b/ar/.config/lf/scope
new file mode 100755
index 0000000..079c519
--- /dev/null
+++ b/ar/.config/lf/scope
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+# File preview handler for lf.
+
+set -C -f
+IFS="$(printf '%b_' '\n')"
+IFS="${IFS%_}"
+
+image() {
+ if [ -f "$1" ] && [ -n "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] && command -V ueberzug >/dev/null 2>&1; then
+ printf '{"action": "add", "identifier": "PREVIEW", "x": "%s", "y": "%s", "width": "%s", "height": "%s", "scaler": "contain", "path": "%s"}\n' "$4" "$(($5 + 1))" "$(($2 - 1))" "$(($3 - 2))" "$1" >"$FIFO_UEBERZUG"
+ mediainfo --Output="Video;%Duration/String%" "$6"
+ else
+ mediainfo "$6"
+ fi
+}
+
+# Note that the cache file name is a function of file information, meaning if
+# an image appears in multiple places across the machine, it will not have to
+# be regenerated once seen.
+
+case "$(file --dereference --brief --mime-type -- "$1")" in
+image/avif)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE" ] && magick "$1" "$CACHE.jpg"
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+image/vnd.djvu)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE" ] && djvused "$1" -e 'select 1; save-page-with /dev/stdout' | magick -density 200 - "$CACHE.jpg" >/dev/null 2>&1
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+image/svg+xml)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE" ] && inkscape --convert-dpi-method=none -o "$CACHE.png" --export-overwrite -D --export-png-color-mode=RGBA_16 "$1"
+ image "$CACHE.png" "$2" "$3" "$4" "$5" "$1"
+ ;;
+image/x-xcf)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | awk '{print $1}')"
+ [ ! -f "$CACHE.jpg" ] && magick "$1[0]" "$CACHE.jpg"
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+image/*) image "$1" "$2" "$3" "$4" "$5" "$1" ;;
+text/html) lynx -width="$4" -display_charset=utf-8 -dump "$1" ;;
+text/troff) man ./ "$1" | col -b ;;
+text/* | */xml | application/json | application/x-ndjson) bat -p --theme ansi --terminal-width "$(($4 - 2))" -f "$1" ;;
+audio/*) mediainfo "$1" || exit 1 ;;
+video/* | application/octet-stream)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE" ] && ffmpegthumbnailer -i "$1" -o "$CACHE" -s 0
+ image "$CACHE" "$2" "$3" "$4" "$5" "$1"
+ ;;
+*/pdf)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE.jpg" ] && pdftoppm -jpeg -f 1 -singlefile "$1" "$CACHE"
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+application/vnd.openxmlformats-officedocument.presentationml.presentation)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE.jpg" ] && unoconv -f jpg -o "$CACHE.jpg" "$1"
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+application/x-hwp)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ OUT_DIR="$(dirname "$CACHE")"
+ [ ! -f "$CACHE.jpg" ] && libreoffice --headless --convert-to jpg --outdir "$OUT_DIR" "$1" && mv "$OUT_DIR/$(basename "$1" .hwp).jpg" "$CACHE.jpg"
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+*/epub+zip | */mobi*)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE.jpg" ] && gnome-epub-thumbnailer "$1" "$CACHE.jpg"
+ image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1"
+ ;;
+application/*zip) atool --list -- "$1" ;;
+*opendocument*) odt2txt "$1" ;;
+application/pgp-encrypted) gpg -d -- "$1" ;;
+application/vnd.openxmlformats-officedocument.wordprocessingml.document)
+ CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)"
+ [ ! -f "$CACHE.txt" ] && pandoc "$1" -t plain -o "$CACHE.txt"
+ bat -p --theme ansi --terminal-width "$(($4 - 2))" -f "$CACHE.txt"
+ ;;
+esac
+exit 1
diff --git a/ar/.config/mimeapps.list b/ar/.config/mimeapps.list
new file mode 100644
index 0000000..77b1f50
--- /dev/null
+++ b/ar/.config/mimeapps.list
@@ -0,0 +1,26 @@
+[Default Applications]
+
+# xdg-open will use these settings to determine how to open filetypes.
+# These .desktop entries can also be seen and changed in ~/.local/share/applications/
+
+application/pdf=pdf.desktop;
+application/postscript=pdf.desktop;
+application/rss+xml=rss.desktop;
+application/x-bittorrent=torrent.desktop;
+application/vnd.openxmlformats-officedocument.presentationml.presentation=office.desktop;
+application/vnd.openxmlformats-officedocument.wordprocessingml.document=office.desktop;
+inode/directory=file.desktop;
+image/jpeg=img.desktop;
+image/gif=img.desktop;
+image/png=img.desktop;
+text/csv=csv.desktop;
+text/markdown=slide.desktop;
+text/plain=text.desktop;
+text/troff=roff.desktop;
+text/x-shellscript=text.desktop;
+text/xml=html.desktop;
+video/mp4=video.desktop;
+video/x-matroska=video.desktop;
+x-scheme-handler/lbry=lbry.desktop;
+x-scheme-handler/mailto=mail.desktop;
+x-scheme-handler/magnet=torrent.desktop;
diff --git a/ar/.config/mpd/mpd.conf b/ar/.config/mpd/mpd.conf
new file mode 100644
index 0000000..a37492b
--- /dev/null
+++ b/ar/.config/mpd/mpd.conf
@@ -0,0 +1,19 @@
+music_directory "~/Music"
+playlist_directory "~/.config/mpd/playlists"
+
+auto_update "yes"
+bind_to_address "127.0.0.1"
+restore_paused "yes"
+max_output_buffer_size "16384"
+
+audio_output {
+ type "pipewire"
+ name "PipeWire Sound Server"
+}
+
+audio_output {
+ type "fifo"
+ name "Visualizer feed"
+ path "/tmp/mpd.fifo"
+ format "44100:16:2"
+}
diff --git a/ar/.config/mpd/playlists/entire.m3u b/ar/.config/mpd/playlists/entire.m3u
new file mode 100644
index 0000000..867c26f
--- /dev/null
+++ b/ar/.config/mpd/playlists/entire.m3u
@@ -0,0 +1,890 @@
+(G)I-DLE - Fate (나는 아픈 건 딱 질색이니까).mp3
+Renai Circulation「恋愛サーキュレーション」歌ってみた【*なみりん】.mp3
+070 Shake - Guilty Conscience.mp3
+10cm - 스토커.mp3
+10CM - Go Back (고백).mp3
+10CM - 고장난걸까 ⧸ 눈물의 여왕 OST Part.2.mp3
+24kGoldn - GAMES ON YOUR PHONE.mp3
+2NE1 - 그리워해요 (Missing You).mp3
+2NE1 - Go Away.mp3
+5 Seconds of Summer - High.mp3
+88rising, Joji, Rich Brian, AUGUST 08, Brian Soewarno, George Miller, Maurice Powell, Ray Jacobs, Zhen Ding - Midsummer Madness.mp3
+ONE OK ROCK 「完全感覚Dreamer」.mp3
+Adam Levine - Lost Stars.mp3
+Adam Turley, Heather Sommer - The Problem.mp3
+Adele - When We Were Young.mp3
+【Ado】うっせぇわ.mp3
+【Ado】ギラギラ.mp3
+【Ado】踊.mp3
+【Ado】新時代 (ウタ from ONE PIECE FILM RED).mp3
+【Ado】逆光(ウタ from ONE PIECE FILM RED).mp3
+【Ado】 唱.mp3
+aespa - Supernova.mp3
+ヒグチアイ ⧸ 悪魔の子 (アニメスペシャルVer.) | Ai Higuchi “Akuma no Ko” Anime Special Ver..mp3
+Aimer「残響散歌」MUSIC VIDEO(テレビアニメ「鬼滅の刃」遊郭編オープニングテーマ).mp3
+aimyon - Ai Wo Tsutaetaidatoka.mp3
+aimyon - Marigold.mp3
+aimyon - Lucky Color.mp3
+Air Supply - Making Love Out Of Nothing At All.mp3
+【TDC】バブリーダンス 登美丘高校ダンス部 Tomioka Dance Club.mp3
+【MV full】 ヘビーローテーション ⧸ AKB48 [公式].mp3
+【MV full】 恋するフォーチュンクッキー ⧸ AKB48[公式].mp3
+AKMU - Way Back Home.mp3
+Alaina Castillo, Sam Roman - pocket locket.mp3
+Alec Benjamin - Let Me Down Slowly.mp3
+Alec Benjamin - Jesus in LA.mp3
+Alec Benjamin - Mind Is A Prison.mp3
+Alexander Oscar - Bad Intentions.mp3
+[Alexandros] - ワタリドリ (MV).mp3
+Alphaville - Forever Young (2019 Remaster).mp3
+Attack on Titan Season 2 - Official Opening Song - Shinzou wo Sasageyo by Linked Horizon.mp3
+Demon Slayer | OP | "Gurenge" by LiSA HD.mp3
+Anson Seabra - Welcome to Wonderland.mp3
+Anson Seabra - I Can't Carry This Anymore.mp3
+Anson Seabra - That's Us.mp3
+Anson Seabra - Trying My Best.mp3
+Ant Saunders - Yellow Hearts.mp3
+Anthony Russo - Thinkin Bout You.mp3
+[Track Video] 양홍원 - 가면무도회.mp3
+ASH ISLAND - Lonely.mp3
+ASH ISLAND - Last Moments.mp3
+ASH ISLAND - Rose In The Heart.mp3
+ASH ISLAND - WONDER.mp3
+ASH ISLAND, BE′O - Bad Words (Feat. BE′O).mp3
+ASH ISLAND, Skinny Brown - Stay With Me (Feat. Skinny Brown).mp3
+「海の声」 フルver. ⧸ 浦島太郎(桐谷健太) 【公式】.mp3
+AAA ⧸ 「恋音と雨空」Music Video.mp3
+和楽器バンド ⧸ 千本桜.mp3
+三代目 J SOUL BROTHERS from EXILE TRIBE ⧸ 「R.Y.U.S.E.I.」Music Video.mp3
+AAA ⧸ 「さよならの前に」Music Video.mp3
+DA PUMP ⧸ U.S.A..mp3
+YOASOBI「夜に駆ける」 Official Music Video.mp3
+YOASOBI「あの夢をなぞって」 Official Music Video.mp3
+YOASOBI「ハルジオン」Official Music Video.mp3
+YOASOBI「たぶん」Official Music Video.mp3
+YOASOBI「群青」Official Music Video.mp3
+YOASOBI「怪物」Official Music Video (YOASOBI - Monster).mp3
+YOASOBI「祝福」Official Music Video (『機動戦士ガンダム 水星の魔女』オープニングテーマ).mp3
+YOASOBI「アイドル」 Official Music Video.mp3
+YOASOBI「勇者」 Official Music Video/TVアニメ『葬送のフリーレン』オープニングテーマ.mp3
+ayokay, Jeremy Zucker - Stay With Me.mp3
+B.I - Dare to Love (feat. BIG Naughty) (겁도 없이 (feat. 서동현)).mp3
+BABYMETAL - ギミチョコ!!- Gimme chocolate!! (OFFICIAL).mp3
+back number - Christmas Song.mp3
+back number - クリスマスソング (full).mp3
+back number - ハッピーエンド (full).mp3
+back number - 高嶺の花子さん (full).mp3
+back number - 水平線.mp3
+back number - Suiheisen.mp3
+BAK, IKE - レプリカ.mp3
+Bazzi - Mine.mp3
+Bazzi - Myself.mp3
+Bazzi - Why.mp3
+Bazzi - Paradise.mp3
+Bazzi - Young & Alive.mp3
+Bazzi - I Got You.mp3
+Bazzi - I Don't Think I'm Okay.mp3
+Bazzi, Camila Cabello - Beautiful (feat. Camila Cabello).mp3
+BE'O - Suddenly.mp3
+BE′O, ASH ISLAND, GRAY - Without You (Feat. ASH ISLAND) (Prod. GRAY).mp3
+Beenzino - So Wet.mp3
+BIGBANG - 천국 (My Heaven).mp3
+BIGBANG - Cafe.mp3
+BIGBANG - 사랑 먼지 (Love Dust).mp3
+Billie Eilish - come out and play.mp3
+BOBBY - Cherry Blossom.mp3
+BOBBY - 20s 30s.mp3
+BOBBY - help me out o kill me not.mp3
+BOBBY, iHwak - moon (Feat. iHwak).mp3
+BOM - Don't Cry.mp3
+Bruno Major, Emily Caroline Elbert - Easily.mp3
+Bruno Major, Daniel Neil McDougall - Home.mp3
+Bruno Major, Raelee Nikole - Nothing.mp3
+Bruno Major, Dan Mcdougall - Old Fashioned.mp3
+Bruno Major, George Bruns - Regent's Park.mp3
+Bruno Major, Finneas O’Connell - The Most Beautiful Thing.mp3
+Bruno Major, Emily Elbert - The Show Must Go On.mp3
+Bruno Major, Finlay Robson, Dan McDougall - We Were Never Really Friends.mp3
+Bruno Mars - Versace on the Floor.mp3
+BTS (방탄소년단) - 봄날.mp3
+BTS (방탄소년단) - Permission to Dance.mp3
+BUMKEY, Loco, SURAN - LALALA (feat. SURAN & Loco) (Deun Ji Mix).mp3
+BUMP OF CHICKEN「天体観測」.mp3
+Calum Scott - You Are The Reason.mp3
+Calvin Harris, Frank Ocean, Migos - Slide.mp3
+CAMO - Shawty (feat. 쿠기 (Coogie)).mp3
+CAMO - Six Weeks.mp3
+CAMO, JMIN - Life is Wet (feat. JMIN).mp3
+Dave Maclean - We Said Goodbye.mp3
+The Chainsmokers, Winona Oak - Hope.mp3
+CHANGMO - D.O.N.mp3
+Charlie Puth - River.mp3
+비몽 - 작야 Live COVER (240201) │ 원곡 조선블루스.mp3
+Chinozo 「グッバイ宣言」 feat.FloweR.mp3
+d4vd - Here With Me (Live) | The Circle° Sessions.mp3
+Cloudybay, ASH ISLAND - Forget (Feat. ASH ISLAND).mp3
+CODE KUNST, CHOI JUNG HOON of JANNABI, Simon Dominic - For the gone (with ELLE KOREA) (사라진 모든 것들에게(with ELLE KOREA)).mp3
+CODE KUNST(코드 쿤스트) - rain bird (비네) (Feat. Tablo & Colde).mp3
+Coldplay - Everglow (Edit).mp3
+Coldplay - Everglow.mp3
+Conan Gray - Affluenza.mp3
+Conan Gray - Heather.mp3
+Conan Gray - Maniac.mp3
+Conan Gray - Memories.mp3
+Conor Maynard - You Broke Me First.mp3
+Coogie, BE'O, GRAY, GRAY, Coogie, BE'O, Coogie, BE'O - Good Night (Feat. BE'O) (Good Night (Feat. BE'O)).mp3
+Coogie, LeeHi - Alone (Feat. LeeHi) (Alone (Feat. 이하이)).mp3
+Cosmic Boy, george - Wishlist (Feat. george).mp3
+Creepy Nuts「Bling-Bang-Bang-Born」 × TV Anime「マッシュル-MASHLE-」 Collaboration Music Video #BBBBダンス.mp3
+Crush - SOFA.mp3
+Crush (크러쉬) - 미안해 미워해 사랑해 ⧸ 눈물의 여왕 OST Part.4.mp3
+Crush - 미안해 미워해 사랑해.mp3
+Damons year - Gestalt (Mondegreen ver.).mp3
+Dan + Shay - Speechless.mp3
+Dan + Shay - I Should Probably Go To Bed.mp3
+Dan + Shay, Justin Bieber - 10,000 Hours.mp3
+Official髭男dism - ノーダウト[Official Video].mp3
+Official髭男dism - Pretender[Official Video].mp3
+Official Hige Dandism - Pretender.mp3
+Official髭男dism - 宿命[Official Video].mp3
+Official髭男dism - イエスタデイ[Official Video].mp3
+Official髭男dism - I LOVE...[Official Video].mp3
+Official髭男dism - Cry Baby[Official Video].mp3
+Official髭男dism - Mixed Nuts [Official Video].mp3
+Official髭男dism - Subtitle [Official Video].mp3
+Daniel Caesar - Always.mp3
+DAOKO × 米津玄師『打上花火』MUSIC VIDEO.mp3
+DAOKO, Kenshi Yonezu - Uchiagehanabi.mp3
+DAY6 - 예뻤어 You Were Beautiful.mp3
+DAY6 - Man in a Movie.mp3
+DAY6 - 그렇더라고요 When you love someone.mp3
+DAY6 - 한 페이지가 될 수 있게 Time of Our Life.mp3
+DAY6 - Zombie.mp3
+DEAN - DIE 4 YOU.mp3
+DEAN, Dok2 - I Love It.mp3
+DECEMBER - 별이될께 (별이될께).mp3
+DECEMBER - Because of love (사랑 참…).mp3
+Doja Cat, The Weeknd - You Right.mp3
+DON McLEAN: "SINCE I DON'T HAVE YOU" (1981).mp3
+dori - 떨림 ⧸ 눈물의 여왕 OST Part.10.mp3
+DPR IAN, DPR LIVE, CL - No Blueberries (No Blueberries (Feat. DPR LIVE, CL)).mp3
+Dynamicduo, E SENS, Kim Yeon Woo - L.B.A (Feat. E - Sens For Supreme Team, Kim Yeon Woo).mp3
+Dynamicduo, Simon Dominic - Missing You (Feat. Simon Dominic For Supreme Team).mp3
+Ed Sheeran - Supermarket Flowers.mp3
+Ed Sheeran - Bad Habits.mp3
+ELI - Wish Now Was Later.mp3
+Ellie Goulding, Lauv - Slow Grenade.mp3
+Epik High - One (Feat. 지선).mp3
+Epik High - 우산 (Feat. 윤하).mp3
+Epik High - Rain Song (Feat. Colde).mp3
+EPIK HIGH - 춥다 (It's Cold) ft. 이하이.mp3
+EPIK HIGH - 빈차 (Home Is Far Away) ft. OH HYUK.mp3
+Etham - 12_45 (Stripped).mp3
+Etham - Opposite Of Loving Me (Stripped).mp3
+Etham - Purpose (Stripped).mp3
+Etham - I Wish It Was Me (Stripped).mp3
+ドラマツルギー - Eve MV.mp3
+お気に召すまま - Eve MV.mp3
+廻廻奇譚 - Eve MV.mp3
+FIFTY FIFTY - Cupid (Twin Version).mp3
+FINNEAS - Let's Fall in Love for the Night.mp3
+LiSA - 紅蓮華 ⧸ THE FIRST TAKE.mp3
+DISH⧸⧸ (北村匠海) - 猫 ⧸ THE FIRST TAKE.mp3
+YOASOBI - 夜に駆ける ⧸ THE HOME TAKE.mp3
+優里 - ドライフラワー ⧸ THE FIRST TAKE.mp3
+YOASOBI - 群青 ⧸ THE FIRST TAKE.mp3
+레오 Leo - 콧시 CozySea cover (원곡:優里 Yuuri - Leo).mp3
+FLOW - Sign.mp3
+FR:EDEN, Jade, 프리든 (FR:EDEN), OPO, 프리든 (FR:EDEN), Jade, OPO, 프리든 (FR:EDEN), Jade - FRONT SEAT (Feat. Jade) (앞자리 (Feat. Jade)).mp3
+Frank Ocean - Self Control.mp3
+フレデリック「オドループ」Music Video | Frederic "oddloop".mp3
+Fujii Kaze - Shinunoga E-Wa.mp3
+Fujii Kaze - Kirari (Official Video).mp3
+G-DRAGON - Missing You ft. 김윤아 of 자우림.mp3
+G-DRAGON - 결국 (Without You) ft. ? Of YG New Girl Group.mp3
+G-DRAGON, TAEYANG, DAESUNG, 24, Jumpa, NOS, Mondee, VVN, G-DRAGON, TEDDY, KUSH, 24, G-DRAGON, TEDDY, CHOICE37 - HOME SWEET HOME (feat. TAEYANG, DAESUNG) (HOME SWEET HOME (feat. 태양, 대성)).mp3
+Gabrielle Aplin & JP Cooper - Losing Me (Piano Version).mp3
+【みうめ・メイリア・217】極楽浄土[Gokuraku Jodo] OFFiCiAL.mp3
+星野源 – 恋 (Official Video).mp3
+GI$T (윤현선) - 바보 Babo (Feat. Leellamarz).mp3
+Gilbert O'Sullivan - A Woman's Place.mp3
+Gilbert O'Sullivan - Our Own Baby.mp3
+Gilbert O'Sullivan - Alone Again (Naturally) [Official Audio].mp3
+GIRIBOY, Jvcki Wai - Tiger Den (Feat. Jvcki Wai) (호랑이소굴 (Feat. Jvcki Wai)).mp3
+GIRIBOY, Woo - That's Why I Can't Talk About Love (Feat. Woo) (That's Why I Can't Talk About Love (Feat.....mp3
+Gist, Leellamarz, DON MALIK - Black Swan (Feat. Leellamarz, DON MALIK).mp3
+Giveon - Heartbreak Anniversary.mp3
+Giveon - Like I Want You.mp3
+Giveon - This Ain't Love.mp3
+Giveon - Stuck On You.mp3
+Green Day - Last Night on Earth.mp3
+GroovyRoom - Yes or No (Feat. 허윤진 of LE SSERAFIM, Crush) (Yes or No (Feat. 허윤진 of LE SSERAFIM,....mp3
+Gyeong Je Hwan - 외딴 별과 위성.mp3
+H1-KEY - Rose Blossom.mp3
+Halsoon - 내 플레이리스트는 온통 너였다 (Feat. HYUN SEO) (Prod. Franken.).mp3
+Han Yo Han - Routine (반복).mp3
+Han Yo Han, NO:EL - It's You (Feat. NO:EL) (It's You (Feat. NO:EL)).mp3
+Han Yo Han(한요한) - L'OCCITANE (록시땅).mp3
+HAN YO HAN(한요한) - Green Bicycle (따릉이).mp3
+HAON, GISELLE - Skrr (feat. GISELLE).mp3
+hard life - dead celebrities.mp3
+Harry Styles - Adore You.mp3
+Harry Styles - Falling.mp3
+Harry Styles - As It Was.mp3
+Hayd - Closure.mp3
+糸 - 中島みゆき(フル).mp3
+YouTubeテーマソング/ヒカキン&セイキン.mp3
+宇多田ヒカル - First Love.mp3
+KANA-BOON 『ないものねだり』Music Video.mp3
+Homies - Drama (prod. Kidstone) (Drama (prod. Kidstone)).mp3
+Homies, Changmo, Kidstone - Eraser in my head (feat. Changmo) (prod. Kidstone) (내 머리속의 지우개(feat. 창모)....mp3
+可愛くてごめん feat. ちゅーたん(CV:早見沙織)/HoneyWorks.mp3
+HONNE - Warm on a Cold Night.mp3
+HONNE - 3am.mp3
+HONNE - Good Together.mp3
+HONNE - Day 1 ◑.mp3
+HONNE - no song without you.mp3
+HONNE - free love.mp3
+HONNE - la la la that’s how it goes.mp3
+HONNE, Izzy Bizu - Someone That Loves You.mp3
+HONNE, JONES - No Place Like Home (feat. JONES).mp3
+HONNE, Tom Misch - Me & You ◑.mp3
+Huh! - MBT.mp3
+iKON - AIRPLANE.mp3
+Imagine Dragons - Thunder.mp3
+Imagine Dragons - Warriors.mp3
+Imagine Dragons - Bad Liar.mp3
+Imagine Dragons, JID, Arcane, League Of Legends - Enemy (from the series Arcane League of Legends).mp3
+Imagine Dragons - Wrecked.mp3
+【imase】NIGHT DANCER(MV).mp3
+imase - Have a nice day.mp3
+imase - NIGHT DANCER.mp3
+In Real Life - Crazy AF.mp3
+My Sweet Lord 2020 Mix (EQ).mp3
+j-hope - on the street (with J. Cole).mp3
+JAEHA, Futuristic Swaver - Save (Feat. Futuristic Swaver).mp3
+Jang Beom June - every moment with you (당신과는 천천히).mp3
+JANNABI - She (SHE (HIDDEN TRACK NO.V 1월 선정곡)).mp3
+JANNABI(잔나비) - Summer (뜨거운 여름밤은 가고 남은 건 볼품없지만).mp3
+JANNABI(잔나비) - for lovers who hesitate (주저하는 연인들을 위해).mp3
+Jax - Ring Pop.mp3
+jeebanoff - Bad girl (나쁜 아이).mp3
+jeebanoff - Come along with me (Come along with me).mp3
+jeebanoff - Me and You (너와 같이).mp3
+jeebanoff - Paper mache (종이인형).mp3
+jeebanoff - The clouds (검은 구름).mp3
+jeebanoff, sogumm - We (OUI) (Feat. sogumm) (We (OUI) (Feat. sogumm)).mp3
+Jehwwn - No Hope For Your Return.mp3
+Jeremy Zucker - all the kids are depressed.mp3
+Jeremy Zucker - end.mp3
+Jeremy Zucker - ghosts.mp3
+Jeremy Zucker - comethru.mp3
+Jeremy Zucker - desire.mp3
+Jeremy Zucker - scared.mp3
+Jeremy Zucker, Chelsea Cutler - you were good to me (bonus track).mp3
+Jeremy Zucker - always, i'll care.mp3
+Jeremy Zucker - not ur friend.mp3
+Jeremy Zucker - supercuts.mp3
+Jeremy Zucker, Chelsea Cutler - emily.mp3
+Jeremy Zucker - OK.mp3
+Joe Hisaishi, London Symphony Orchestra - Summer.mp3
+John K - Rum n Tequila.mp3
+John K - parachute.mp3
+Johnny Stimson - Flower.mp3
+Joji, George Miller - ATTENTION.mp3
+Jooyoung - Samcheong View (삼청 View) (Feat. pH-1).mp3
+Jooyoung, Jo Hyun Ah, Beenzino - Door (Feat. Beenzino) (Door (Feat. Beenzino)).mp3
+Josef Salvat - call on me.mp3
+JP Saxe, Julia Michaels - Kissin' In The Cold.mp3
+Juice WRLD - Legends.mp3
+Juliander - Nervous.mp3
+JUNNY - MOVIE (MOVIE).mp3
+JUSTHIS, KWAII, DON MALIK, GongGongGoo009 - Do Not Go Gentle Into That Good Night (Feat. KWAII, DON MALIK, GongGongGoo009) (Prod. Humbert)....mp3
+Justin Bieber - Ghost.mp3
+Justin Bieber - Hold On.mp3
+Justin Bieber, Chance The Rapper - Holy.mp3
+Justin Bieber, Daniel Caesar, Giveon - Peaches.mp3
+Justin Bieber, Post Malone, Clever - Forever.mp3
+Justin Bieber, Quavo - Intentions.mp3
+K.will, MC Mong - LOVE119 (feat. MC Mong) (러브119 (Feat. MC몽)).mp3
+TVアニメ「ダンベル何キロ持てる?」OPテーマ Muscle Video.mp3
+KANA-BOON - Silhouette.mp3
+Kanye West - Jail.mp3
+Kanye West, PARTYNEXTDOOR - Ghost Town.mp3
+Kanye West, XXXTENTACION - True Love.mp3
+KCM - 안녕.mp3
+KCM - An old love story (흑백사진).mp3
+Keem Hyo-Eun - Baby (Feat. ZENE THE ZILLA).mp3
+Keem Hyo-Eun - Come Back (Feat. Leellamarz, Hash Swan, Skinny Brown).mp3
+Keira Knightley - Like A Fool.mp3
+米津玄師 - アイネクライネ , Kenshi Yonezu - Eine Kleine.mp3
+米津玄師 - LOSER , Kenshi Yonezu.mp3
+米津玄師 - orion , Kenshi Yonezu.mp3
+米津玄師 - ピースサイン , Kenshi Yonezu - Peace Sign.mp3
+米津玄師 - 灰色と青( +菅田将暉 ), Kenshi Yonezu - Haiirotoao(+Masaki Suda).mp3
+米津玄師 - 春雷 Kenshi Yonezu - Shunrai.mp3
+米津玄師 Kenshi Yonezu - Lemon.mp3
+米津玄師 - Flamingo ⧸ Kenshi Yonezu.mp3
+米津玄師 - 海の幽霊 Kenshi Yonezu - Spirits of the Sea.mp3
+米津玄師 - パプリカ Kenshi Yonezu - PAPRIKA.mp3
+米津玄師 - 馬と鹿 Kenshi Yonezu - Uma to Shika.mp3
+米津玄師 - 感電 Kenshi Yonezu - KANDEN.mp3
+Kenshi Yonezu - Kanden.mp3
+Kenshi Yonezu - Lemon.mp3
+Kenshi Yonezu - Peace Sign.mp3
+Kenshi Yonezu - KICK BACK.mp3
+米津玄師 Kenshi Yonezu - KICKBACK.mp3
+Kenshi Yonezu - LADY.mp3
+Kenshi Yonezu - Garakuta - JUNK.mp3
+keshi - less of you.mp3
+keshi - more.mp3
+keshi - always.mp3
+keshi - right here.mp3
+keshi - 2 soon.mp3
+keshi - good days.mp3
+Khalid - Saturday Nights.mp3
+Khalid, Disclosure - Talk.mp3
+The Kid LAROI - WHAT JUST HAPPENED.mp3
+The Kid LAROI, Jung Kook, Central Cee - TOO MUCH.mp3
+Kid Milli - Outro (Feat. GIRIBOY) (기리보이).mp3
+Kid Wine - Stay With Me (Feat. Leellamarz, Bassagong, The Quiett).mp3
+Kina, Snøw - Get You The Moon.mp3
+Kina, Adriana Proenza - Can We Kiss Forever?.mp3
+King & Prince「ツキヨミ」YouTube Edit.mp3
+King Gnu - 白日.mp3
+King Gnu - 一途.mp3
+King Gnu - 逆夢.mp3
+King Gnu - SPECIALZ.mp3
+【女性が歌う】Lemon⧸米津玄師(Full Covered by コバソロ & 春茶).mp3
+KYLE, Kehlani - Playinwitme (feat. Kehlani).mp3
+KYLE, Teyana Taylor - F You I Love You (feat. Teyana Taylor).mp3
+KYUL - Broken.mp3
+KYUL - Someone You Used To Know.mp3
+KYUL - Still the Same.mp3
+KYUL - Feel Better.mp3
+LANY - Flowers On The Floor.mp3
+LANY - Let Me Know.mp3
+LANY - Malibu Nights.mp3
+LANY - up to me.mp3
+LANY - dna (demo).mp3
+LANY - Love At First Fight.mp3
+Lauv - 26.mp3
+Lauv, Ari Leff, Brandon Lowry, Michael Matosic, Michael Pollack, Jonathan Simpson - Sims.mp3
+Lauv, Ari Leff, Michael Matosic - I Like Me Better.mp3
+Lauv, Ari Leff, Michael Matosic, Michael Pollack - Changes.mp3
+Lauv, Ari Leff, Michael Matosic, Michael Pollack, Jonathan Bellion, Jonathan Simpson - Drugs & The Internet.mp3
+Lauv, LANY, Ari Leff, Michael Matosic, Michael Pollack, Paul Jason Klein, John Hill, Jordan Palmer - Mean It.mp3
+Lauv, Michael Pollack, Ari Leff, Jonathan Simpson, Andrea Rosario - Feelings.mp3
+Leellamarz - If I got money (feat. Crucial Star, CHANGMO).mp3
+Leellamarz - Mr. complain.mp3
+Leellamarz, ASH ISLAND - On the street (Feat. ASH ISLAND).mp3
+Leellamarz, CAMO - I Like U (Feat. CAMO).mp3
+Leellamarz, CHANGMO - B**ch (Feat. CHANGMO).mp3
+Leellamarz, Gist - Uncomfortable (Feat. Gist).mp3
+Leellamarz, TOIL - Don′t do That.mp3
+Leellamarz, Young B - Hell yea.mp3
+LeeSSang, Jung In - 리쌍부르쓰 (feat. 정인).mp3
+【妖怪ウォッチ】ようかい体操第一.mp3
+Liam Gallagher - All You're Dreaming Of (Demo Version).mp3
+Lil Nas X - SUN GOES DOWN.mp3
+Lil Tjay, 6LACK - Calling My Phone.mp3
+Lim Jae Hyun - Rhapsody of Sadness.mp3
+Lizzo, Ariana Grande - Good as Hell (feat. Ariana Grande) (Remix).mp3
+Loco - NOT OK (Feat. MINNIE ((G)I-DLE)).mp3
+Loco - PIKPIK.mp3
+Loco, Crush - Still (Feat. Crush).mp3
+Loote - are you sure?.mp3
+Lorde - Liability.mp3
+Lukas Graham - 7 Years.mp3
+Lukas Graham - Happy Home.mp3
+Lukas Graham - HERE (For Christmas).mp3
+M.C The Max - No matter where (어디에도).mp3
+M.C The Max - Wind that Blows (그대가 분다).mp3
+M.C the MAX(엠씨더맥스) - After You’ve Gone (넘쳐흘러).mp3
+Mac Miller - Come Back to Earth.mp3
+Mac Miller - Good News.mp3
+Madison Beer - Selfish.mp3
+Mae Muller - Anticlimax.mp3
+Maluma, The Weeknd - Hawái (Remix).mp3
+『チェンソーマン』ノンクレジットオープニング ⧸ CHAINSAW MAN Opening│米津玄師 「KICK BACK」.mp3
+Maroon 5 - Sunday Morning.mp3
+Maroon 5 - Lovesick.mp3
+Maroon 5 - Nobody's Love.mp3
+Maroon 5 - Seasons.mp3
+Martin Garrix, Troye Sivan - There for You.mp3
+Tokyo Drift - Teriyaki Boyz [ MUSIC VIDEO ] HD.mp3
+Matt Terry - Sucker for You.mp3
+MAX, Chromeo, Dave Macklovitch, Imad Royal, Maxwell Schneider, Patrick Gemayel, Rogét Chahayed, Sam Fischer, Sean Douglas - Checklist (feat. Chromeo).mp3
+MC MONG, Chancellor - Love mash (feat.Chancellor of the channels) (사랑 범벅 (FEAT.챈슬러 OF THE CHANNELS)).mp3
+MC MONG, Gary, Hyorin of Sistar - Broken fan (feat.Gary, Hyorin of Sistar) (고장난 선풍기 (FEAT.개리,효린 OF 씨스타)).mp3
+MC MONG, Yang Dail - The happiest time of my life Part.2 (Feat. Yang Da Il).mp3
+MC 몽 & 서인영 - Bubble Love.mp3
+Melanie Martinez - Training Wheels.mp3
+MELOH - IDNY (Feat. Skinny Brown).mp3
+MELOH - Understand (Feat. GIST).mp3
+MELOH, Boi B - SMILE (Feat. Boi B) (Prod. WOOGIE) (SMILE (Feat. 보이비) (Prod. WOOGIE)).mp3
+MELOH, GRAY - Droppa (Feat. GRAY) (Droppa (Feat. GRAY)).mp3
+MELOH, MELOH, Agwi - Love of My Life.mp3
+MeloMance - You (From Sugar Man 2, Pt. 2).mp3
+Miki Matsubara - 真夜中のドア⧸Stay With Me.mp3
+美波「カワキヲアメク」MV.mp3
+Mrs. GREEN APPLE - 青と夏.mp3
+Mrs. GREEN APPLE - 僕のこと.mp3
+Mrs. GREEN APPLE - インフェルノ(Inferno).mp3
+Mrs. GREEN APPLE「ダンスホール」Official Music Video.mp3
+N.Flying - Rooftop (옥탑방 (Rooftop)).mp3
+N.Flying - Oh really. (아 진짜요. (Oh really.)).mp3
+なにわ男子 - 初心LOVE(うぶらぶ)[Music Video Dance ver.].mp3
+New Hope Club - Worse.mp3
+New Hope Club, Danna Paola - Know Me Too Well.mp3
+New Hope Club, R3HAB - Let Me Down Slow.mp3
+NewJeans - Bubble Gum.mp3
+【パプリカ】ダンス ミュージックビデオ | Foorin×米津玄師 | NHK.mp3
+NiziU 『Make you happy』 M⧸V.mp3
+NiziU(니쥬) Debut Single『Step and a step』MV.mp3
+Noel Gallagher's High Flying Birds - Dead In The Water (Live At RTÉ 2FM Studios, Dublin).mp3
+Noel Gallagher's High Flying Birds, Noel Gallagher - In the Heat of the Moment.mp3
+Noel Gallagher's High Flying Birds, Noel Gallagher - Pretty Boy.mp3
+Noel Gallagher's High Flying Birds, Noel Gallagher - Open The Door, See What You Find.mp3
+Noel Gallagher's High Flying Birds, Noel Gallagher - We're Gonna Get There In The End.mp3
+Novelbright - Aitoka Koitoka.mp3
+Novelbright, Yutaro,Yudai, Yudai - Walking with you.mp3
+Oasis - Wonderwall (Remastered).mp3
+Oasis - Don't Look Back in Anger (Remastered).mp3
+Oasis - Live Forever (Remastered).mp3
+Oasis - Whatever (Remastered).mp3
+Oasis - Little by Little.mp3
+Oasis - Let There Be Love.mp3
+Oasis, Noel Gallagher - Stand By Me (Remastered).mp3
+Official Hige Dandism - ノーダウト.mp3
+Official Hige Dandism - 宿命.mp3
+Official Hige Dandism - イエスタデイ.mp3
+Official HIGE DANdism - Cry Baby.mp3
+Official HIGE DANdism - Universe.mp3
+OFFICIAL HIGE DANDISM - Subtitle.mp3
+OFFICIAL HIGE DANDISM - TATTOO.mp3
+OFFICIAL HIGE DANDISM - SOULSOUP.mp3
+OFFICIAL HIGE DANDISM - ホワイトノイズ.mp3
+OFFICIAL HIGE DANDISM - 日常.mp3
+Official髭男dism - 115万キロのフィルム.mp3
+offonoff (오프온오프) offonoff - Good2me (Feat. PUNCHNELLO).mp3
+offonoff (오프온오프) offonoff - boy.mp3
+offonoff (오프온오프) offonoff - gold (Feat. Dean).mp3
+offonoff (오프온오프) offonoff - Moon, 12_04am.mp3
+Olivia Rodrigo - vampire (Official Lyric Video).mp3
+One Direction - What Makes You Beautiful.mp3
+One Direction - One Thing.mp3
+ONE OK ROCK - The Beginning [Official Music Video].mp3
+ONE OK ROCK - Clock Strikes [Official Music Video].mp3
+<ノラガミARAGOTO>OPテーマ THE ORAL CIGARETTES「狂乱 Hey Kids!!」MusicVideo.mp3
+OuiOui - Moonlight.mp3
+OuiOui (위위) - Maybe I.mp3
+OuiOui, Wilcox - Thinkin′ bout you (Feat. Wilcox).mp3
+PATEKO (파테코), Jayci Yucca(제이씨 유카), Kid Wine - 널 떠올리는 중이야.mp3
+Paul Blanco - Summer (Feat. BE’O (비오)) [Summer]ㅣLyrics⧸가사.mp3
+Paul Young - Every Time You Go Away (Radio Edit).mp3
+Peder Elias - Back in the Game.mp3
+PENOMECO, Crush - No.5 (feat. Crush).mp3
+Pink Sweat$ - I Feel Good.mp3
+Pink Sweat$, Crush - I Wanna Be Yours.mp3
+Post Malone - 92 Explorer.mp3
+Post Malone - Better Now.mp3
+Post Malone - Candy Paint.mp3
+Post Malone - Paranoid.mp3
+Post Malone - Rich & Sad.mp3
+Post Malone - Stay.mp3
+Post Malone - I Fall Apart.mp3
+Post Malone - White Iverson.mp3
+Post Malone - A Thousand Bad Times.mp3
+Post Malone - Allergic.mp3
+Post Malone - Circles.mp3
+Post Malone - I'm Gonna Be.mp3
+Post Malone - Myself.mp3
+Post Malone - When I’m Alone.mp3
+Post Malone - Wrapped Around Your Finger.mp3
+Post Malone - Chemical.mp3
+Post Malone - Mourning.mp3
+Post Malone - Overdrive.mp3
+Post Malone - Speedometer.mp3
+Post Malone, 2 Chainz - Money Made Me Do It.mp3
+Post Malone, Blake Shelton - Pour Me A Drink.mp3
+Post Malone, DaBaby - Enemies.mp3
+Post Malone, Justin Bieber - Deja Vu.mp3
+Post Malone, Luke Combs - Guy For That.mp3
+Post Malone, Morgan Wallen - I Had Some Help.mp3
+Post Malone, Roddy Ricch - Cooped Up.mp3
+Post Malone, Swae Lee - Sunflower (Spider-Man: Into the Spider-Verse).mp3
+Post Malone, SZA - Staring At The Sun.mp3
+Post Malone, The Weeknd - One Right Now.mp3
+Post Malone, Young Thug - Goodbyes.mp3
+Powfu - im used to it.mp3
+Powfu, beabadoobee - death bed (coffee for your head).mp3
+Powfu, Sarcastic Sounds, Rxseboy - ill come back to you.mp3
+Primary, Beenzino - Too Far (Feat. Beenzino).mp3
+Primary, BUMKEY, Paloalto - LOVE (Feat. Bumkey, Paloalto).mp3
+Primary, E SENS - poison (Feat. E-Sens Of Supreme Team).mp3
+Primary, Kim Sung Kyu - Drama (Feat. Kim Sung Kyu).mp3
+The Quiett - 귀감 gui gam (Feat. ZENE THE ZILLA).mp3
+Quinn XCII, Marc E. Bassy - Coffee.mp3
+QWER, Lee DongHyuk, HunGi Hong, BuildingOwner, Lee DongHyuk, HunGi Hong, Elum, GESTURE, Seungjun Kim, Hong Ji Hye, Lee Siyeon, Lee DongHyuk, GESTURE, Elum - Discord (Discord).mp3
+Radiohead - Fake Plastic Trees.mp3
+Radiohead - Creep.mp3
+Radiohead - No Surprises (Remastered).mp3
+RADWIMPS - 前前前世 (movie ver.) [Official Music Video].mp3
+スパークル [original ver.] -Your name. Music Video edition- 予告編 from new album「人間開花」初回盤DVD.mp3
+RADWIMPS - 愛にできることはまだあるかい [Official Music Video].mp3
+Regard, Troye Sivan, Tate McRae - You.mp3
+Retroriron, Suzune, Suzune, Retroriron - 深夜6時.mp3
+Frankie Valli - Can't Take My Eyes Off You (Official Audio).mp3
+ROLE MODEL - hello!.mp3
+Ruel - Younger.mp3
+Ruel - Painkiller.mp3
+Ruel - Face To Face.mp3
+Ruel - Real Thing.mp3
+Ruel - as long as you care.mp3
+Ruel, Cautious Clay - say it over.mp3
+Russ - Psycho, Pt. 2.mp3
+Vardy's on Fire - The S6 [Official Lyric Video].mp3
+Sabrina Carpenter, Jonas Blue - Alien.mp3
+Sam Kim(샘김) - Make Up (Feat. Crush).mp3
+Sam Smith - Palace.mp3
+Sam Smith - Too Good At Goodbyes.mp3
+Sasha Alex Sloan - Normal.mp3
+Sasha Alex Sloan - Older.mp3
+Sasha Alex Sloan - Dancing With Your Ghost.mp3
+Sasha Alex Sloan - Too Sad To Cry.mp3
+Saucy Dog「シンデレラボーイ」Music Video <5th Mini Album「レイジーサンデー」2021.8.25 Release>.mp3
+SEKAI NO OWARI「RPG」.mp3
+SEKAI NO OWARI「Habit」.mp3
+SEVENTEEN - F*ck My Life.mp3
+SG 워너비 - 라라라 메달.mp3
+SG워너비 (SG Wanna Be) - 사랑하길 정말 잘했어요.mp3
+SG 워너비 - 해바라기.mp3
+SG워너비 (SG Wanna Be) - Timeless.mp3
+SG Wannabe - 아리랑.mp3
+SG워너비 (SG Wanna Be) - 세글자.mp3
+SG 워너비 SG WANNABE - 광 (狂).mp3
+SG 워너비 SG WANNABE - 살다가.mp3
+SG 워너비 SG WANNABE - 죄와 벌.mp3
+SG워너비 - 내사람 : Partner for Life.mp3
+SixTONES – こっから [YouTube ver.].mp3
+Shawn Mendes - Where Were You In The Morning?.mp3
+Shawn Mendes - If I Can't Have You.mp3
+Silica Gel - NO PAIN.mp3
+Silica Gel - Mercurial.mp3
+Silica Gel - Andre99.mp3
+Simon Dominic, ELO - Lonely Night (Feat. ELO) (GRAY Remix).mp3
+Skinny Brown - Sing Alone.mp3
+Skinny Brown - Seoul Rendezvous.mp3
+Skinny Brown - Fix you (1393).mp3
+Skinny Brown, TOIL - Don't Flex on me (Feat. jayci yucca, 김효은 Keem Hyo-Eun).mp3
+Slander, Dylan Matthew - Love Is Gone (Acoustic).mp3
+Snow Man「D.D.」MV (YouTube ver.).mp3
+菅田将暉 『さよならエレジー』.mp3
+Soovi, pH-1 - Make the Move (feat. pH-1).mp3
+God knows... ''The Melancholy of Haruhi Suzumiya'' 【涼宮ハルヒの憂鬱】Kadokawa公認MAD【ベース 演奏】.mp3
+【感情を込めて】ハレハレヤ 歌ってみた ver.Sou.mp3
+スピッツ ⧸ ロビンソン.mp3
+スピッツ ⧸ チェリー.mp3
+SPYAIR - I Wanna Be....mp3
+SPYAIR - La la la....mp3
+SPYAIR - Some Like It Hot!!.mp3
+SPYAIR - Genjyou Destruction.mp3
+SPYAIR - I Want A Place.mp3
+SPYAIR - Little Summer.mp3
+SPYAIR - My World.mp3
+SPYAIR - Goldship.mp3
+SPYAIR - Kimigaitanatsu.mp3
+SPYAIR - Scramble.mp3
+SPYAIR - Just Like This.mp3
+SPYAIR - Liar.mp3
+SPYAIR - Ameagarinisakuhana.mp3
+SPYAIR - Winding Road.mp3
+SPYAIR - Beautiful.mp3
+SPYAIR - Last Moment.mp3
+SPYAIR - My Friend.mp3
+SPYAIR - Stay Together.mp3
+SPYAIR - Sakuramitsutsuki.mp3
+Stephen Bishop - On And On (Official Lyric Video).mp3
+Stephen Sanchez - Until I Found You (Official Video).mp3
+sunwoojunga - 도망가자.mp3
+Superfly 『愛をこめて花束を』Music Video.mp3
+Supreme Team, Crush - You Can Stay (Feat. Crush).mp3
+Supreme Team, Gaeko - Only You (Feat. Gaeko).mp3
+SURAN(수란) - WINE (오늘 취하면) (Feat.Changmo) (창모) (Prod. SUGA).mp3
+Surfaces - Good Day.mp3
+SZA, Doja Cat - Kill Bill.mp3
+TAEYEON - Fine.mp3
+TAEYEON - To. X.mp3
+Tani Yuuki - W ⧸ X ⧸ Y.mp3
+W⧸X⧸Y - Tani Yuuki (Official Lyric Video).mp3
+Tani Yuuki - Cheers.mp3
+Tatsuya Kitani - Where Our Blue Is.mp3
+Teriyaki Boyz - Tokyo Drift (Fast & Furious) (From "The Fast And The Furious: Tokyo Drift" Soundtrack).mp3
+THAMA - Nuuh.mp3
+I love to love - Tina Charles.mp3
+TOIL - Spring (feat. ASH ISLAND, Skinny Brown).mp3
+TOIL, Heize, BIG Naughty - walk again (Feat. Heize, BIG Naughty).mp3
+TOIL, kid wine - He's next to you, instead of me.mp3
+TOIL, Leellamarz, B.I - I Wish (Feat. Leellamarz, B.I).mp3
+TOIL, Leellamarz, BE′O - Black Heart (Feat. Leellamarz, BE′O).mp3
+TOIL, Skinny Brown, CHANGMO - I Don′t Care (Feat. Skinny Brown, CHANGMO).mp3
+TOIL, Skinny Brown, Owen - Me (Feat. Skinny Brown, Owen).mp3
+ノンテロップスペシャル版 TVアニメ「東京喰種トーキョーグール」オープニング映像 TK from 凛として時雨⧸unravel.mp3
+Toy, Beenzino, Crush - U & I (With Crush & Beenzino) (U & I (With Crush & 빈지노)).mp3
+Trippie Redd - Love Sick.mp3
+Troye Sivan - SUBURBIA.mp3
+Troye Sivan - Bloom.mp3
+Troye Sivan - Lucky Strike.mp3
+Troye Sivan - Strawberries & Cigarettes.mp3
+Troye Sivan - IN A DREAM.mp3
+Troye Sivan - Rager teenager!.mp3
+Troye Sivan, Ariana Grande - Dance To This.mp3
+Troye Sivan, Jay Som - Trouble (from ‘Three Months’).mp3
+Tyla - Water.mp3
+Tyla - Truth or Dare.mp3
+Tyler Shaw - Love You Still (abcdefu romantic Version).mp3
+back number - 「クリスマスソング」Music Video.mp3
+URBAN ZAKAPA(어반자카파) - Seoul Night (서울 밤) (feat. Beenzino) (빈지노).mp3
+URBANZAKAPA(어반자카파) - When we were two (그때의 나, 그때의 우리).mp3
+【Official】Uru 『あなたがいることで』TBS系 日曜劇場「テセウスの船」主題歌.mp3
+V.O.S - Again (다시 만날까 봐).mp3
+V.O.S, Kim Subin(AIMING) Cho Sehee(AIMING) Kwon soohyun(AIMING), Kim Changrock(AIMING) Kim Subin(AIMING), Changrock(AIMING) Kim Subin(AIMING) Kim Minjae(ONCLASSA) Kyeong won Yun(ONCLASSA) - Like Crazy (미친 것처럼).mp3
+Valley - Like 1999.mp3
+Valley - Oh shit…are we in love?.mp3
+不可幸力 ⧸ Vaundy :MUSIC VIDEO.mp3
+Vaundy - Bye by me.mp3
+Vaundy - life hack.mp3
+Vaundy - napori.mp3
+Vaundy - 不可幸力.mp3
+Vaundy - 怪獣の花唄.mp3
+Vaundy - 東京フラッシュ.mp3
+怪獣の花唄 ⧸ Vaundy : MUSIC VIDEO.mp3
+Vaundy - 世界の秘密.mp3
+다시 듣고 싶어, 너의 노래가🌻: Vaundy - 괴수의 꽃노래 [가사⧸발음⧸해석].mp3
+【第75回NHK紅白歌合戦 歌唱曲】踊り子 ⧸ Vaundy:MUSIC VIDEO.mp3
+Vaundy - 恋風邪にのせて.mp3
+Vaundy - mabataki.mp3
+Vaundy - そんなbitterな話.mp3
+Vaundy - 忘れ物.mp3
+Vaundy - 花占い.mp3
+Vaundy - 融解sink.mp3
+Vaundy - 風神.mp3
+VINXEN, OVAN, Im Soo - JUNG (정 (Prod. By VAN.C)).mp3
+VINXEN, 김종완 of 넬 - Star (feat.Nell) (별(feat.김종완 of 넬) (prod.BOYCOLD)).mp3
+Gravity | Live by W2RDO.mp3
+W2RDO 위도 - My Little Bird.mp3
+W2RDO 위도 - 수피 樹皮.mp3
+W2RDO 위도 - Shivers.mp3
+W2RDO 위도 - Until I Found You.mp3
+W2RDO 위도 - 소로 小路.mp3
+W2RDO 위도 - 그대만 있다면.mp3
+마마 | Live by W2RDO.mp3
+WANIMA - ともに (OFFICIAL VIDEO).mp3
+きゃりーぱみゅぱみゅ - PONPONPON , Kyary Pamyu Pamyu - PONPONPON.mp3
+The Weeknd - Blinding Lights.mp3
+The Weeknd - In Your Eyes.mp3
+The Weeknd - Save Your Tears.mp3
+The Weeknd - Less Than Zero.mp3
+The Weeknd - Out of Time.mp3
+The Weeknd - Dancing In The Flames.mp3
+The Weeknd, Ariana Grande - Save Your Tears (Remix).mp3
+The Weeknd, Ariana Grande - Die For You (Remix).mp3
+The Weeknd, Tyler, The Creator - Here We Go… Again.mp3
+Wiz Khalifa, Charlie Puth - See You Again (feat. Charlie Puth).mp3
+WizTheMc - For A Minute.mp3
+WOODZ - Multiply.mp3
+WOODZ, NATHAN, HOHO, WOODZ, NATHAN, HOHO, WOODZ - Drowning (Drowning).mp3
+Supreme Team ft. Soulman - Please Tell Me [HD] [Eng Sub].mp3
+XXXTENTACION - Jocelyn Flores.mp3
+XXXTENTACION - Looking for a Star.mp3
+XXXTENTACION - Moonlight.mp3
+XXXTENTACION - SAD!.mp3
+XXXTENTACION - changes.mp3
+XXXTENTACION - Ex Bitch.mp3
+XXXTENTACION, PnB Rock, Trippie Redd - bad vibes forever (feat. PnB Rock & Trippie Redd).mp3
+yama - 春を告げる (Official Video).mp3
+Yerin Baek - Rest.mp3
+Yerin Baek - Square (2017).mp3
+YOASOBI, Ayase - 夜に駆ける.mp3
+YOASOBI, Ayase - アイドル.mp3
+神山羊 - YELLOW【Music Video】⧸ Yoh Kamiyama - YELLOW.mp3
+Young B(영비) - ROSE (Feat. Skinny Brown, Homeboy).mp3
+Yugo Kanno, Yugo Kanno, Yugo Kanno - il vento d'oro.mp3
+Yuuri - BETELGEUSE.mp3
+Zedd, Kehlani - Good Thing.mp3
+ZENE THE ZILLA, MELOH - Dancing in the rain.mp3
+ZICO, LUNA - It Was Love (Feat. LUNA).mp3
+Zion.T, Dok2 - Click Me (Feat. Dok2).mp3
+가비 엔제이 ⧸ Gavy Nj - Happiness.mp3
+GUMMY(거미) - MEMORY LOSS(기억상실) M⧸V.mp3
+[MV] KyoungSeo(경서) _ 120BPM(첫 키스에 내 심장은 120BPM).mp3
+KWON EUN BI, TOYO, TOYO, Gabriel Brandes, Arineh Karim, Cho yun kyoung - Underwater (Underwater).mp3
+Kim Na Young(김나영) - As you told me (니 말대로).mp3
+Kim Na Young(김나영) - Never (그럴리가).mp3
+Kim Na Young(김나영) - Sometimes (가끔 내가).mp3
+Kim Na Young(김나영) - The Way (길).mp3
+Kim Na Young(김나영) - Watch memories (꺼내본다).mp3
+Kim Na Young(김나영) - What if it was going (어땠을까).mp3
+Kim NaYoung(김나영) - Say Goodbye (가슴이 말해).mp3
+Kim Na Young - But I Must (헤어질 수 밖에).mp3
+Kim Na Young(김나영) - End of Love (우리 서로).mp3
+Kim Na Young, 빨간양말, 빨간양말, 굿초이스, 빨간양말 - Because I only see you (그대만 보여서).mp3
+Kim Na Young(김나영) - A sad night (널 떠올리는 밤).mp3
+Kim Na Young(김나영) - I Can't Say That (그 한마디).mp3
+Kim Na Young(김나영) - May be (그럴지도 몰라).mp3
+Kim Na Young(김나영) - forget (이제 그만).mp3
+Kim na young(김나영) - To be honest (솔직하게 말해서 나).mp3
+김나영 Kim na young - 조금 더 외로워지겠지 I get a little bit lonely.mp3
+Kim Na Young, Yang Da Il - Goodbye list (헤어진 우리가 지켜야 할 것들).mp3
+Kim Na Young - Not Anyone Else (다른 누구 말고 너야).mp3
+Kim Na Young - Farewell Poem (니가 없다면).mp3
+Kim Na Young, Jungkey, Park Chan, Jungkey, Jungkey - I Can't Help It (Prod. Jungkey) (어쩔 수가 없나 봐 (Prod. 정키)).mp3
+김나영 - 일기 ⧸ 눈물의 여왕 OST Part.7.mp3
+김수현 - 청혼 ⧸ 눈물의 여왕 OST Special Track.mp3
+김태래 (ZEROBASEONE) - 더 바랄게 없죠 ⧸ 눈물의 여왕 OST Part.11.mp3
+Noel - Missing you (그리워 그리워).mp3
+Noel - 청혼.mp3
+Noel - 전부 너였다 Everything was You.mp3
+Noel(노을) - How about you (너는 어땠을까).mp3
+Noel - Late Night (늦은 밤 너의 집 앞 골목길에서).mp3
+Noel - Things that I couldn't say  (하지 못한 말).mp3
+Noel - What if (만약에 말야 (전우성 SOLO)).mp3
+Noel - still around you (너의 곁에만 맴돌아).mp3
+더 넛츠 ⧸ The NuTs - 내 사람입니다.mp3
+더 넛츠 ⧸ The NuTs - 잔소리.mp3
+더 넛츠 ⧸ The NuTs - 사랑의 바보.mp3
+리무카이쵸 - 너에게 마지막 입맞춤을(君に最後の口づけを) [자막⧸가사].mp3
+LiSA - Crossing Field.mp3
+LiSA 『紅蓮華』 -MUSiC CLiP YouTube EDIT ver.-.mp3
+LiSA 『炎』 -MUSiC CLiP-.mp3
+[심묘] 마음짓기⧸하나땅.mp3
+Vibe, All Christian - My Star (feat. All Christian).mp3
+백예린 Yerin Baek - 야간비행 (魔女の花) Merry And The Witch’s Flower.mp3
+부석순 (SEVENTEEN) - 자꾸만 웃게 돼 ⧸ 눈물의 여왕 OST Part.1.mp3
+Beemong(비몽) Sings Mama ⧸ 마마 (by Kimfeelsun ⧸ 김필선, +Lyrics).mp3
+BIBI - Bam Yang Gang (밤양갱).mp3
+Billy Joel - Honesty (Official Video).mp3
+BIGMAMA - 체념 (After Resignatoion ⧸ Retiment).mp3
+BIGMAMA - 연 (捐) (Year).mp3
+Big Mama, Kim Changrock, Kang Minhun, Kwon Soohyun, Kim Changrock, Kim Subin, Cho Sehee, Kim Changrock, Choi Songhee - One Day More (하루만 더).mp3
+Big Mama - Like nothing happened (아무렇지 않은 척).mp3
+소수빈 - Last Chance ⧸ 눈물의 여왕 OST Part.8.mp3
+스트레이 - 너, 너.mp3
+pH-1(피에치원) - Cupid(큐피드)(Feat. PENOMECO(페노메코)) 가사ㅣLyricㅣsmay.mp3
+SHIN YONG JAE(4MEN) - Reason (이유).mp3
+SHIN YONG JAE - Feel You.mp3
+SHIN YONG JAE (2F) - The Four Seasons (사계 (四季)).mp3
+테이 - 같은 베개.mp3
+[Official lyric video] 심규선 - 야래향 夜來香.mp3
+[MV] 심규선 - 수피 樹皮.mp3
+[Official Audio] 심규선 - My Little Bird (ENG⧸JPN⧸CHI sub).mp3
+IU - The shower (푸르던).mp3
+IU - Ending Scene (이런 엔딩).mp3
+IU - dlwlrma (이 지금).mp3
+IU - Autumn morning (가을 아침).mp3
+IU - Love poem (Love poem).mp3
+IU - Rain Drop (Rain Drop).mp3
+J-Cera - 사랑시 고백구 행복동 (사랑시 고백구 행복동).mp3
+IU, Donghwan Seo, IU, IU - Winter Sleep (겨울잠).mp3
+AKMU - ‘롱디 (Long D)’ (Official Audio).mp3
+[MV] YEEUN AHN(안예은) _ Night Flower(야화).mp3
+안예은 - 만개화 [웹툰 '화산귀환'] [가사⧸Lyrics].mp3
+Yang Dail(양다일), Jungkey(정키) - We’re different (우린 알아).mp3
+Yang Da Il(양다일) - Think (널).mp3
+[MV] 우효(OOHYO) _ 청춘(Youth) (DAY).mp3
+Yang Da Il(양다일) - Don't Leave (떠나지마).mp3
+Yang Da Il(양다일) - hasty (섣부르진 않니).mp3
+Yang Da Il(양다일) - if i (너를 잊으면).mp3
+Yang Da Il(양다일) - lie (미안해).mp3
+Yang Da Il(양다일) - mistaken (착각).mp3
+Yang Da Il(양다일) - regret (곁에 있는 너).mp3
+Yang Da Il(양다일) - won’t you say it (잘 지내고 있는 거니).mp3
+양다일 (Yang Da Il) - 고백 (sorry)ㅣ가사 Lyrics.mp3
+Yang Da Il(양다일) - sorry (고백).mp3
+Yang Da Il - My Love (My Love).mp3
+양다일 - 이해 ⧸ 가사.mp3
+Yang Da Il - understand (이해).mp3
+양다일 - 요즘 ⧸ 가사.mp3
+양다일 (Yang Da Il) - 널 그리워하고 있어 (Absence).mp3
+Yang Da Il - You can do that (One of a Kind Romance).mp3
+양다일 - 괴로워 ⧸ Kpop ⧸ Lyrics ⧸ 가사.mp3
+Yang Da Il - Wish I could tell you (괴로워).mp3
+엠씨더맥스 (M.C The Max) - One Love.mp3
+엠투엠 ⧸ M To M - Good Bye.mp3
+M TO M - 세글자.mp3
+이무진 - 에피소드 [가사⧸Lyrics].mp3
+LEE MU JIN - Episode (에피소드).mp3
+이수 - My Way.mp3
+이예준 - 그날에 나는 맘이 편했을까 ⧸ 가사.mp3
+Lim Jung Hee - 나 돌아가.mp3
+Lim Jung Hee - 눈물이 안났어.mp3
+자이로 (zai.ro) - 너를 그려 Couldn't Forget You.mp3
+Zion.T - UNLOVE (prod. HONNE).mp3
+정효빈 - 이제는 어떻게 사랑을 하나요.mp3
+죠지 - Boat.mp3
+죠지 쿠기 (Coogie) - 아무 말.mp3
+주시크 - 너를 생각해 ⧸ 가사.mp3
+ZIA - let’s have a drink (술 한잔 해요).mp3
+창모 (CHANGMO) - BOY.mp3
+최유리 - 숲 [가사⧸Lyrics].mp3
+최유리 - Promise ⧸ 눈물의 여왕 OST Part.9.mp3
+카더가든 - 나무 (나무).mp3
+크루셜스타 - come to my stu (Feat. 릴러말즈) (Remix).mp3
+탑현 - 사랑한다고 말해줘 ⧸ Kpop ⧸ Lyrics ⧸ 가사.mp3
+탑현 - T의 연애 M⧸V.mp3
+테이 (Tei) - 그리움을 사랑한 가시나무.mp3
+테이 (Tei) - 닮은 사람.mp3
+테이 (Tei) - 사랑은... 향기를 남기고.mp3
+4MEN - 사랑해도 괜찮니.mp3
+4MEN, Navi - Baby You (Feat. Navi) (Baby You (Feat. 나비)).mp3
+4MEN, MIIII - Can't (Feat. MIIII) (못해 (Feat. 美)).mp3
+4MEN, Lee Young Hyun - Living in the Memories (Feat. Lee Young Hyun) (추억에 살아 (Feat. 이영현)).mp3
+4MEN(포맨) - I regret it (후회한다).mp3
+4MEN(포맨) - Baby Baby.mp3
+4MEN(포맨) - Can I Love Again (다시 사랑 할 수 있을까) (feat. Davichi) (다비치).mp3
+4MEN - 고백.mp3
+4MEN - 넌 나의 집.mp3
+4MEN - Once While Living (살다가 한번쯤).mp3
+4MEN - Say I Love You (Say I Love You).mp3
+4MEN - U (U).mp3
+폴킴 - 좋아해요 ⧸ 눈물의 여왕 OST Part.6.mp3
+프라이머리 Primary - 입장정리 Friendzone (Feat. 최자 CHOIZA, Simon D).mp3
+프라이머리 Primary - When I fall in love (Feat. Meego, 수란 SURAN).mp3
+FLY TO THE SKY - You You You (너를 너를 너를).mp3
+Fly To The Sky - Ga Sm A Pa Do.mp3
+Fly to the Sky - Missing You.mp3
+Fly to the Sky - Sea of Love.mp3
+Fly To The Sky - 남자답게 (남자답게).mp3
+[한글자막] 원피스 필름 레드 삽입곡 Full - 나는 최강(I'm invincible) │ Ado.mp3
+HuhGak - Snow Of April (사월의 눈).mp3
+HuhGak(허각) - Miss you (혼자, 한잔).mp3
+Huh Gak - How did we (우린 어쩌다 헤어진 걸까).mp3
+헤이즈 (Heize) - And July (Feat. DEAN & DJ Friz).mp3
+헤이즈 (Heize) - 멈춰줘 ⧸ 눈물의 여왕 OST Part.3.mp3
+홍이삭 - Fallin' ⧸ 눈물의 여왕 OST Part.5.mp3
+あいみょん - 愛を伝えたいだとか 【OFFICIAL MUSIC VIDEO】.mp3
+あいみょん - マリーゴールド【OFFICIAL MUSIC VIDEO】.mp3
+あいみょん – ハルノヒ【OFFICIAL MUSIC VIDEO】.mp3
+あいみょん - 裸の心【OFFICIAL MUSIC VIDEO】.mp3
+ゲスの極み乙女「私以外私じゃないの」.mp3
+ケツメイシ『バラード』.mp3
+ケツメイシ「友よ 〜 この先もずっと…」.mp3
+コブクロ - 蕾.mp3
+サカナクション ⧸ 新宝島 -Music Video-.mp3
+スキマスイッチ - 「奏(かなで)」Music Video : SUKIMASWITCH ⧸ KANADE Music Video.mp3
+ずっと真夜中でいいのに。『秒針を噛む』MV (ZUTOMAYO – Byoushinwo Kamu).mp3
+なとり - Overdose.mp3
+命に嫌われている。/まふまふ【歌ってみた】.mp3
+ヨルシカ - 言って。(Music Video).mp3
+ヨルシカ - ただ君に晴れ (MUSIC VIDEO).mp3
+ヨルシカ - だから僕は音楽を辞めた (Music Video).mp3
+ヨルシカ - 花に亡霊(OFFICIAL VIDEO).mp3
+ロクデナシ「ただ声一つ」⧸ Rokudenashi - One Voice【Official Music Video】.mp3
+優里『ドライフラワー』Official Music Video -ディレクターズカットver.-.mp3
+【オリジナルPV】 小さな恋のうた / MONGOL800(cover) by天月.mp3
+はみがきのうた《東京ハイジ》.mp3
+香水 ⧸ 瑛人 (Official Music Video).mp3
+緑黄色社会 『Shout Baby』Music Video(TVアニメ『僕のヒーローアカデミア』4期「文化祭編」EDテーマ ⧸ MY HERO ACADEMIA ENDING).mp3
+菅田将暉 『まちがいさがし』.mp3
+菅田将暉 『虹』.mp3
diff --git a/ar/.config/mpd/playlists/jpop.m3u b/ar/.config/mpd/playlists/jpop.m3u
new file mode 100644
index 0000000..ae2d3cf
--- /dev/null
+++ b/ar/.config/mpd/playlists/jpop.m3u
@@ -0,0 +1,156 @@
+NA - Renai Circulation「恋愛サーキュレーション」歌ってみた【*なみりん】.mp3
+NA - ONE OK ROCK 「完全感覚Dreamer」.mp3
+NA - 【Ado】うっせぇわ.mp3
+NA - 【Ado】ギラギラ.mp3
+NA - 【Ado】踊.mp3
+NA - 【Ado】新時代 (ウタ from ONE PIECE FILM RED).mp3
+NA - 【Ado】逆光(ウタ from ONE PIECE FILM RED).mp3
+NA - 【Ado】 唱.mp3
+NA - ヒグチアイ ⧸ 悪魔の子 (アニメスペシャルVer.) | Ai Higuchi “Akuma no Ko” Anime Special Ver..mp3
+NA - Aimer「残響散歌」MUSIC VIDEO(テレビアニメ「鬼滅の刃」遊郭編オープニングテーマ).mp3
+aimyon - Ai Wo Tsutaetaidatoka.mp3
+NA - 【TDC】バブリーダンス 登美丘高校ダンス部 Tomioka Dance Club.mp3
+NA - 【MV full】 ヘビーローテーション ⧸ AKB48 [公式].mp3
+NA - 【MV full】 恋するフォーチュンクッキー ⧸ AKB48[公式].mp3
+NA - [Alexandros] - ワタリドリ (MV).mp3
+NA - Attack on Titan Season 2 - Official Opening Song - Shinzou wo Sasageyo by Linked Horizon.mp3
+NA - Demon Slayer | OP | "Gurenge" by LiSA HD.mp3
+NA - 「海の声」 フルver. ⧸ 浦島太郎(桐谷健太) 【公式】.mp3
+NA - AAA ⧸ 「恋音と雨空」Music Video.mp3
+NA - 和楽器バンド ⧸ 千本桜.mp3
+NA - 三代目 J SOUL BROTHERS from EXILE TRIBE ⧸ 「R.Y.U.S.E.I.」Music Video.mp3
+NA - AAA ⧸ 「さよならの前に」Music Video.mp3
+NA - DA PUMP ⧸ U.S.A..mp3
+NA - YOASOBI「夜に駆ける」 Official Music Video.mp3
+NA - YOASOBI「あの夢をなぞって」 Official Music Video.mp3
+NA - YOASOBI「ハルジオン」Official Music Video.mp3
+NA - YOASOBI「たぶん」Official Music Video.mp3
+NA - YOASOBI「群青」Official Music Video.mp3
+NA - YOASOBI「怪物」Official Music Video (YOASOBI - Monster).mp3
+NA - YOASOBI「祝福」Official Music Video (『機動戦士ガンダム 水星の魔女』オープニングテーマ).mp3
+NA - YOASOBI「アイドル」 Official Music Video.mp3
+NA - YOASOBI「勇者」 Official Music Video/TVアニメ『葬送のフリーレン』オープニングテーマ.mp3
+NA - BABYMETAL - ギミチョコ!!- Gimme chocolate!! (OFFICIAL).mp3
+NA - back number - クリスマスソング (full).mp3
+NA - back number - ハッピーエンド (full).mp3
+NA - back number - 高嶺の花子さん (full).mp3
+NA - back number - 水平線.mp3
+NA - BUMP OF CHICKEN「天体観測」.mp3
+NA - Chinozo 「グッバイ宣言」 feat.FloweR.mp3
+NA - Creepy Nuts「Bling-Bang-Bang-Born」 × TV Anime「マッシュル-MASHLE-」 Collaboration Music Video #BBBBダンス.mp3
+NA - DAOKO × 米津玄師『打上花火』MUSIC VIDEO.mp3
+NA - ドラマツルギー - Eve MV.mp3
+NA - お気に召すまま - Eve MV.mp3
+NA - 廻廻奇譚 - Eve MV.mp3
+NA - LiSA - 紅蓮華 ⧸ THE FIRST TAKE.mp3
+NA - DISH⧸⧸ (北村匠海) - 猫 ⧸ THE FIRST TAKE.mp3
+NA - YOASOBI - 夜に駆ける ⧸ THE HOME TAKE.mp3
+NA - 優里 - ドライフラワー ⧸ THE FIRST TAKE.mp3
+NA - YOASOBI - 群青 ⧸ THE FIRST TAKE.mp3
+NA - FLOW - Sign.mp3
+NA - フレデリック「オドループ」Music Video | Frederic "oddloop".mp3
+Fujii Kaze - Shinunoga E-Wa.mp3
+NA - Fujii Kaze - Kirari (Official Video).mp3
+NA - 【みうめ・メイリア・217】極楽浄土[Gokuraku Jodo] OFFiCiAL.mp3
+NA - 星野源 – 恋 (Official Video).mp3
+NA - 糸 - 中島みゆき(フル).mp3
+NA - YouTubeテーマソング/ヒカキン&セイキン.mp3
+NA - 宇多田ヒカル - First Love.mp3
+NA - KANA-BOON 『ないものねだり』Music Video.mp3
+NA - 可愛くてごめん feat. ちゅーたん(CV:早見沙織)/HoneyWorks.mp3
+NA - 【imase】NIGHT DANCER(MV).mp3
+NA - TVアニメ「ダンベル何キロ持てる?」OPテーマ Muscle Video.mp3
+NA - KANA-BOON - Silhouette.mp3
+Kenshi Yonezu - Lemon.mp3
+NA - 米津玄師 - アイネクライネ , Kenshi Yonezu - Eine Kleine.mp3
+NA - 米津玄師 - LOSER , Kenshi Yonezu.mp3
+NA - 米津玄師 - orion , Kenshi Yonezu.mp3
+NA - 米津玄師 - ピースサイン , Kenshi Yonezu - Peace Sign.mp3
+NA - 米津玄師 - 灰色と青( +菅田将暉 ), Kenshi Yonezu - Haiirotoao(+Masaki Suda).mp3
+NA - 米津玄師 - 春雷 Kenshi Yonezu - Shunrai.mp3
+NA - 米津玄師 Kenshi Yonezu - Lemon.mp3
+NA - 米津玄師 - Flamingo ⧸ Kenshi Yonezu.mp3
+NA - 米津玄師 - 海の幽霊 Kenshi Yonezu - Spirits of the Sea.mp3
+NA - 米津玄師 - パプリカ Kenshi Yonezu - PAPRIKA.mp3
+NA - 米津玄師 - 馬と鹿 Kenshi Yonezu - Uma to Shika.mp3
+NA - 米津玄師 - 感電 Kenshi Yonezu - KANDEN.mp3
+NA - King & Prince「ツキヨミ」YouTube Edit.mp3
+NA - King Gnu - 白日.mp3
+NA - King Gnu - 逆夢.mp3
+NA - 【女性が歌う】Lemon⧸米津玄師(Full Covered by コバソロ & 春茶).mp3
+NA - 【妖怪ウォッチ】ようかい体操第一.mp3
+NA - LiSA - Crossing Field.mp3
+NA - LiSA 『紅蓮華』 -MUSiC CLiP YouTube EDIT ver.-.mp3
+NA - LiSA 『炎』 -MUSiC CLiP-.mp3
+Maluma, The Weeknd - Hawái (Remix).mp3
+NA - Tokyo Drift - Teriyaki Boyz [ MUSIC VIDEO ] HD.mp3
+Miki Matsubara - 真夜中のドア⧸Stay With Me.mp3
+NA - 美波「カワキヲアメク」MV.mp3
+NA - Mrs. GREEN APPLE - 青と夏.mp3
+NA - Mrs. GREEN APPLE - 僕のこと.mp3
+NA - Mrs. GREEN APPLE - インフェルノ(Inferno).mp3
+NA - Mrs. GREEN APPLE「ダンスホール」Official Music Video.mp3
+NA - なにわ男子 - 初心LOVE(うぶらぶ)[Music Video Dance ver.].mp3
+NA - 【パプリカ】ダンス ミュージックビデオ | Foorin×米津玄師 | NHK.mp3
+Official Hige Dandism - Pretender.mp3
+NA - Official髭男dism - ノーダウト[Official Video].mp3
+NA - Official髭男dism - Pretender[Official Video].mp3
+NA - Official髭男dism - 宿命[Official Video].mp3
+NA - Official髭男dism - イエスタデイ[Official Video].mp3
+NA - Official髭男dism - I LOVE...[Official Video].mp3
+NA - Official髭男dism - Cry Baby[Official Video].mp3
+NA - Official髭男dism - Mixed Nuts [Official Video].mp3
+NA - Official髭男dism - Subtitle [Official Video].mp3
+NA - <ノラガミARAGOTO>OPテーマ THE ORAL CIGARETTES「狂乱 Hey Kids!!」MusicVideo.mp3
+NA - WANIMA - ともに (OFFICIAL VIDEO).mp3
+NA - RADWIMPS - 前前前世 (movie ver.) [Official Music Video].mp3
+NA - スパークル [original ver.] -Your name. Music Video edition- 予告編 from new album「人間開花」初回盤DVD.mp3
+NA - RADWIMPS - 愛にできることはまだあるかい [Official Music Video].mp3
+NA - Saucy Dog「シンデレラボーイ」Music Video <5th Mini Album「レイジーサンデー」2021.8.25 Release>.mp3
+NA - SEKAI NO OWARI「RPG」.mp3
+NA - SEKAI NO OWARI「Habit」.mp3
+NA - SixTONES – こっから [YouTube ver.].mp3
+NA - Snow Man「D.D.」MV (YouTube ver.).mp3
+NA - 菅田将暉 『さよならエレジー』.mp3
+NA - God knows... ''The Melancholy of Haruhi Suzumiya'' 【涼宮ハルヒの憂鬱】Kadokawa公認MAD【ベース 演奏】.mp3
+NA - 【感情を込めて】ハレハレヤ 歌ってみた ver.Sou.mp3
+NA - スピッツ ⧸ ロビンソン.mp3
+NA - スピッツ ⧸ チェリー.mp3
+NA - Superfly 『愛をこめて花束を』Music Video.mp3
+NA - W⧸X⧸Y - Tani Yuuki (Official Lyric Video).mp3
+Teriyaki Boyz - Tokyo Drift (Fast & Furious) (From "The Fast And The Furious: Tokyo Drift" Soundtrack).mp3
+NA - ノンテロップスペシャル版 TVアニメ「東京喰種トーキョーグール」オープニング映像 TK from 凛として時雨⧸unravel.mp3
+NA - back number - 「クリスマスソング」Music Video.mp3
+NA - 【Official】Uru 『あなたがいることで』TBS系 日曜劇場「テセウスの船」主題歌.mp3
+NA - 不可幸力 ⧸ Vaundy :MUSIC VIDEO.mp3
+NA - 怪獣の花唄 ⧸ Vaundy : MUSIC VIDEO.mp3
+NA - きゃりーぱみゅぱみゅ - PONPONPON , Kyary Pamyu Pamyu - PONPONPON.mp3
+NA - yama - 春を告げる (Official Video).mp3
+YOASOBI, Ayase - 夜に駆ける.mp3
+YOASOBI, Ayase - アイドル.mp3
+NA - 神山羊 - YELLOW【Music Video】⧸ Yoh Kamiyama - YELLOW.mp3
+Yugo Kanno, Yugo Kanno, Yugo Kanno - il vento d'oro.mp3
+NA - あいみょん - 愛を伝えたいだとか 【OFFICIAL MUSIC VIDEO】.mp3
+NA - あいみょん - マリーゴールド【OFFICIAL MUSIC VIDEO】.mp3
+NA - あいみょん – ハルノヒ【OFFICIAL MUSIC VIDEO】.mp3
+NA - あいみょん - 裸の心【OFFICIAL MUSIC VIDEO】.mp3
+NA - ゲスの極み乙女「私以外私じゃないの」.mp3
+NA - ケツメイシ『バラード』.mp3
+NA - ケツメイシ「友よ 〜 この先もずっと…」.mp3
+NA - コブクロ - 蕾.mp3
+NA - サカナクション ⧸ 新宝島 -Music Video-.mp3
+NA - スキマスイッチ - 「奏(かなで)」Music Video : SUKIMASWITCH ⧸ KANADE Music Video.mp3
+NA - ずっと真夜中でいいのに。『秒針を噛む』MV (ZUTOMAYO – Byoushinwo Kamu).mp3
+NA - なとり - Overdose.mp3
+NA - 命に嫌われている。/まふまふ【歌ってみた】.mp3
+NA - ヨルシカ - 言って。(Music Video).mp3
+NA - ヨルシカ - ただ君に晴れ (MUSIC VIDEO).mp3
+NA - ヨルシカ - だから僕は音楽を辞めた (Music Video).mp3
+NA - ヨルシカ - 花に亡霊(OFFICIAL VIDEO).mp3
+NA - ロクデナシ「ただ声一つ」⧸ Rokudenashi - One Voice【Official Music Video】.mp3
+NA - 優里『ドライフラワー』Official Music Video -ディレクターズカットver.-.mp3
+NA - 【オリジナルPV】 小さな恋のうた / MONGOL800(cover) by天月.mp3
+NA - はみがきのうた《東京ハイジ》.mp3
+NA - 香水 ⧸ 瑛人 (Official Music Video).mp3
+NA - 菅田将暉 『まちがいさがし』.mp3
+NA - 菅田将暉 『虹』.mp3
diff --git a/ar/.config/mpd/playlists/kpop.m3u b/ar/.config/mpd/playlists/kpop.m3u
new file mode 100644
index 0000000..ae99c23
--- /dev/null
+++ b/ar/.config/mpd/playlists/kpop.m3u
@@ -0,0 +1,113 @@
+4MEN - 사랑해도 괜찮니.mp3
+4MEN - 고백.mp3
+4MEN - 넌 나의 집.mp3
+4MEN - Once While Living (살다가 한번쯤).mp3
+4MEN - Say I Love You (Say I Love You).mp3
+4MEN - U (U).mp3
+4MEN, Lee Young Hyun - Living in the Memories (Feat. Lee Young Hyun) (추억에 살아 (Feat. 이영현)).mp3
+4MEN, MIIII - Can't (Feat. MIIII) (못해 (Feat. 美)).mp3
+4MEN, Navi - Baby You (Feat. Navi) (Baby You (Feat. 나비)).mp3
+4MEN(포맨) - I regret it (후회한다).mp3
+4MEN(포맨) - Baby Baby.mp3
+4MEN(포맨) - Can I Love Again (다시 사랑 할 수 있을까) (feat. Davichi) (다비치).mp3
+BIBI - Bam Yang Gang (밤양갱).mp3
+Big Mama - Like nothing happened (아무렇지 않은 척).mp3
+Big Mama, Kim Changrock, Kang Minhun, Kwon Soohyun, Kim Changrock, Kim Subin, Cho Sehee, Kim Changrock, Choi Songhee - One Day More (하루만 더).mp3
+BIGMAMA - 체념 (After Resignatoion ⧸ Retiment).mp3
+BIGMAMA - 연 (捐) (Year).mp3
+Crush - 미안해 미워해 사랑해.mp3
+DECEMBER - 별이될께 (별이될께).mp3
+DECEMBER - Because of love (사랑 참…).mp3
+레오 Leo - 콧시 CozySea cover (원곡:優里 Yuuri - Leo).mp3
+Fly to the Sky - Missing You.mp3
+Fly to the Sky - Sea of Love.mp3
+Fly To The Sky - Ga Sm A Pa Do.mp3
+Fly To The Sky - 남자답게 (남자답게).mp3
+FLY TO THE SKY - You You You (너를 너를 너를).mp3
+Huh Gak - How did we (우린 어쩌다 헤어진 걸까).mp3
+HuhGak - Snow Of April (사월의 눈).mp3
+HuhGak(허각) - Miss you (혼자, 한잔).mp3
+IU - The shower (푸르던).mp3
+IU - Ending Scene (이런 엔딩).mp3
+IU - dlwlrma (이 지금).mp3
+IU - Autumn morning (가을 아침).mp3
+IU - Love poem (Love poem).mp3
+IU - Rain Drop (Rain Drop).mp3
+IU, Donghwan Seo, IU, IU - Winter Sleep (겨울잠).mp3
+J-Cera - 사랑시 고백구 행복동 (사랑시 고백구 행복동).mp3
+KCM - 안녕.mp3
+KCM - An old love story (흑백사진).mp3
+Kim Na Young - But I Must (헤어질 수 밖에).mp3
+Kim Na Young - Not Anyone Else (다른 누구 말고 너야).mp3
+Kim Na Young - Farewell Poem (니가 없다면).mp3
+Kim Na Young, Jungkey, Park Chan, Jungkey, Jungkey - I Can't Help It (Prod. Jungkey) (어쩔 수가 없나 봐 (Prod. 정키)).mp3
+Kim Na Young, Yang Da Il - Goodbye list (헤어진 우리가 지켜야 할 것들).mp3
+Kim Na Young, 빨간양말, 빨간양말, 굿초이스, 빨간양말 - Because I only see you (그대만 보여서).mp3
+Kim na young(김나영) - To be honest (솔직하게 말해서 나).mp3
+Kim Na Young(김나영) - As you told me (니 말대로).mp3
+Kim Na Young(김나영) - Never (그럴리가).mp3
+Kim Na Young(김나영) - Sometimes (가끔 내가).mp3
+Kim Na Young(김나영) - The Way (길).mp3
+Kim Na Young(김나영) - Watch memories (꺼내본다).mp3
+Kim Na Young(김나영) - What if it was going (어땠을까).mp3
+Kim Na Young(김나영) - End of Love (우리 서로).mp3
+Kim Na Young(김나영) - A sad night (널 떠올리는 밤).mp3
+Kim Na Young(김나영) - I Can't Say That (그 한마디).mp3
+Kim Na Young(김나영) - May be (그럴지도 몰라).mp3
+Kim Na Young(김나영) - forget (이제 그만).mp3
+Kim NaYoung(김나영) - Say Goodbye (가슴이 말해).mp3
+LEE MU JIN - Episode (에피소드).mp3
+Lim Jung Hee - 나 돌아가.mp3
+Lim Jung Hee - 눈물이 안났어.mp3
+M TO M - 세글자.mp3
+Noel - Missing you (그리워 그리워).mp3
+Noel - 청혼.mp3
+Noel - 전부 너였다 Everything was You.mp3
+Noel - Late Night (늦은 밤 너의 집 앞 골목길에서).mp3
+Noel - Things that I couldn't say  (하지 못한 말).mp3
+Noel - What if (만약에 말야 (전우성 SOLO)).mp3
+Noel - still around you (너의 곁에만 맴돌아).mp3
+Noel(노을) - How about you (너는 어땠을까).mp3
+SG Wannabe - 아리랑.mp3
+SG 워너비 - 라라라 메달.mp3
+SG 워너비 - 해바라기.mp3
+SG 워너비 SG WANNABE - 광 (狂).mp3
+SG 워너비 SG WANNABE - 살다가.mp3
+SG 워너비 SG WANNABE - 죄와 벌.mp3
+SG워너비 - 내사람 : Partner for Life.mp3
+SG워너비 (SG Wanna Be) - 사랑하길 정말 잘했어요.mp3
+SG워너비 (SG Wanna Be) - Timeless.mp3
+SG워너비 (SG Wanna Be) - 세글자.mp3
+SHIN YONG JAE - Feel You.mp3
+SHIN YONG JAE (2F) - The Four Seasons (사계 (四季)).mp3
+SHIN YONG JAE(4MEN) - Reason (이유).mp3
+sunwoojunga - 도망가자.mp3
+V.O.S - Again (다시 만날까 봐).mp3
+V.O.S, Kim Subin(AIMING) Cho Sehee(AIMING) Kwon soohyun(AIMING), Kim Changrock(AIMING) Kim Subin(AIMING), Changrock(AIMING) Kim Subin(AIMING) Kim Minjae(ONCLASSA) Kyeong won Yun(ONCLASSA) - Like Crazy (미친 것처럼).mp3
+Vibe, All Christian - My Star (feat. All Christian).mp3
+Yang Da Il - My Love (My Love).mp3
+Yang Da Il - understand (이해).mp3
+Yang Da Il - You can do that (One of a Kind Romance).mp3
+Yang Da Il - Wish I could tell you (괴로워).mp3
+Yang Da Il(양다일) - Think (널).mp3
+Yang Da Il(양다일) - Don't Leave (떠나지마).mp3
+Yang Da Il(양다일) - hasty (섣부르진 않니).mp3
+Yang Da Il(양다일) - if i (너를 잊으면).mp3
+Yang Da Il(양다일) - lie (미안해).mp3
+Yang Da Il(양다일) - mistaken (착각).mp3
+Yang Da Il(양다일) - regret (곁에 있는 너).mp3
+Yang Da Il(양다일) - won’t you say it (잘 지내고 있는 거니).mp3
+Yang Da Il(양다일) - sorry (고백).mp3
+Yang Dail(양다일), Jungkey(정키) - We’re different (우린 알아).mp3
+ZIA - let’s have a drink (술 한잔 해요).mp3
+Zion.T - UNLOVE (prod. HONNE).mp3
+가비 엔제이 ⧸ Gavy Nj - Happiness.mp3
+김나영 Kim na young - 조금 더 외로워지겠지 I get a little bit lonely.mp3
+더 넛츠 ⧸ The NuTs - 내 사람입니다.mp3
+더 넛츠 ⧸ The NuTs - 잔소리.mp3
+더 넛츠 ⧸ The NuTs - 사랑의 바보.mp3
+양다일 (Yang Da Il) - 널 그리워하고 있어 (Absence).mp3
+엠투엠 ⧸ M To M - Good Bye.mp3
+테이 (Tei) - 그리움을 사랑한 가시나무.mp3
+테이 (Tei) - 닮은 사람.mp3
+테이 (Tei) - 사랑은... 향기를 남기고.mp3
diff --git a/ar/.config/mpd/playlists/pop.m3u b/ar/.config/mpd/playlists/pop.m3u
new file mode 100644
index 0000000..6c2a87b
--- /dev/null
+++ b/ar/.config/mpd/playlists/pop.m3u
@@ -0,0 +1,56 @@
+Alaina Castillo, Sam Roman - pocket locket.mp3
+Alec Benjamin - Let Me Down Slowly.mp3
+Anson Seabra - Welcome to Wonderland.mp3
+Anson Seabra - I Can't Carry This Anymore.mp3
+Anson Seabra - That's Us.mp3
+Anson Seabra - Trying My Best.mp3
+Bruno Major, Dan Mcdougall - Old Fashioned.mp3
+Bruno Major, Daniel Neil McDougall - Home.mp3
+Bruno Major, Emily Caroline Elbert - Easily.mp3
+Bruno Major, Emily Elbert - The Show Must Go On.mp3
+Bruno Major, Finlay Robson, Dan McDougall - We Were Never Really Friends.mp3
+Bruno Major, Finneas O’Connell - The Most Beautiful Thing.mp3
+Bruno Major, George Bruns - Regent's Park.mp3
+Bruno Major, Raelee Nikole - Nothing.mp3
+Calum Scott - You Are The Reason.mp3
+The Chainsmokers, Winona Oak - Hope.mp3
+Coldplay - Everglow (Edit).mp3
+Conor Maynard - You Broke Me First.mp3
+Doja Cat, The Weeknd - You Right.mp3
+FINNEAS - Let's Fall in Love for the Night.mp3
+Giveon - Heartbreak Anniversary.mp3
+Giveon - Heartbreak Anniversary.mp3
+Green Day - Last Night on Earth.mp3
+GroovyRoom - Yes or No (Feat. 허윤진 of LE SSERAFIM, Crush) (Yes or No (Feat. 허윤진 of LE SSERAFIM,....mp3
+Hayd - Closure.mp3
+Imagine Dragons - Thunder.mp3
+Imagine Dragons - Warriors.mp3
+Imagine Dragons - Bad Liar.mp3
+Imagine Dragons - Wrecked.mp3
+Imagine Dragons, JID, Arcane, League Of Legends - Enemy (from the series Arcane League of Legends).mp3
+Jax - Ring Pop.mp3
+Jax - Ring Pop.mp3
+Jeremy Zucker - scared.mp3
+Jeremy Zucker, Chelsea Cutler - you were good to me (bonus track).mp3
+Jeremy Zucker, Chelsea Cutler - emily.mp3
+Kina, Adriana Proenza - Can We Kiss Forever?.mp3
+Kina, Snøw - Get You The Moon.mp3
+Loote - are you sure?.mp3
+Lorde - Liability.mp3
+Lukas Graham - 7 Years.mp3
+Lukas Graham - 7 Years.mp3
+Mae Muller - Anticlimax.mp3
+Maluma, The Weeknd - Hawái (Remix).mp3
+Sasha Alex Sloan - Normal.mp3
+Sasha Alex Sloan - Older.mp3
+Sasha Alex Sloan - Dancing With Your Ghost.mp3
+Sasha Alex Sloan - Too Sad To Cry.mp3
+Slander, Dylan Matthew - Love Is Gone (Acoustic).mp3
+SZA, Doja Cat - Kill Bill.mp3
+Tyla - Water.mp3
+Tyla - Truth or Dare.mp3
+Tyler Shaw - Love You Still (abcdefu romantic Version).mp3
+The Weeknd - Blinding Lights.mp3
+The Weeknd - In Your Eyes.mp3
+The Weeknd - Save Your Tears.mp3
+Wiz Khalifa, Charlie Puth - See You Again (feat. Charlie Puth).mp3
diff --git a/ar/.config/mpv/input.conf b/ar/.config/mpv/input.conf
new file mode 100644
index 0000000..276d6ec
--- /dev/null
+++ b/ar/.config/mpv/input.conf
@@ -0,0 +1,114 @@
+# MBTN_LEFT script-binding visibility # Toggle progress bar
+MBTN_LEFT_DBL script-binding visibility # Toggle progress bar
+MBTN_MID cycle fullscreen # Cycle fullscreen
+MBTN_RIGHT cycle pause # Toggle pause/playback mode
+MBTN_BACK playlist-prev # Skip to the previous file
+MBTN_FORWARD playlist-next # Skip to the next file
+enter cycle fullscreen # Cycle fullscreen
+right no-osd seek 1 exact; script-message-to misc show-position # Seek exactly 1 second forward
+shift+right no-osd seek 30 exact; script-message-to misc show-position # Seek exactly 30 second forward
+d no-osd seek 1 exact; script-message-to misc show-position # Seek exactly 1 second forward
+ctrl+d no-osd seek 180 exact; script-message-to misc show-position # Seek exactly 180 second backward
+shift+d no-osd seek 30 exact; script-message-to misc show-position # Seek exactly 30 second backward
+alt+ctrl+d script-message-to delete_current_file delete-file # Delete file without confirmation
+alt+shift+d script-message-to delete_current_file delete-file y "press 'y' to delete file" # Delete file
+left no-osd seek -1 exact; script-message-to misc show-position # Seek exactly 1 second backward
+shift+left no-osd seek -30 exact; script-message-to misc show-position # Seek exactly 30 second backward
+a no-osd seek -1 exact; script-message-to misc show-position # Seek exactly 1 second backward
+ctrl+a no-osd seek -180 exact; script-message-to misc show-position # Seek exactly 180 second backward
+shift+a no-osd seek -30 exact; script-message-to misc show-position # Seek exactly 30 second backward
+v script-binding showplaylist # Playlist in current path
+shift+v script-binding navigator # Playlist in all path
+alt+v vf toggle vflip # Flip vertically
+c no-osd seek 15 exact; script-message-to misc show-position # Seek exactly 5 seconds forward
+ctrl+c add video-zoom 0.1 # Zoom in
+shift+c no-osd seek 60 exact; script-message-to misc show-position # Seek exactly 5 seconds forward
+z no-osd seek -15 exact; script-message-to misc show-position # Seek exactly 5 seconds backward
+ctrl+z add video-zoom -0.1 # Zoom out
+shift+z no-osd seek -60 exact; script-message-to misc show-position # Seek exactly 5 seconds backward
+q no-osd sub-seek -1 # Seek to the previous subtitle
+ctrl+q add chapter -1 # Seek -chapters
+shift+q cycle sub down # Switch subtitle track backwards
+e no-osd sub-seek 1 # Seek to the next subtitle
+ctrl+e add chapter 1 # Seek +chapters
+shift+e cycle sub # Switch subtitle track
+space cycle pause # Toggle pause/playback mode
+s cycle pause # Toggle pause/playback mode
+PRINT script-binding crop-screenshot # Crop screenshot
+ctrl+l script-message-to misc cycle-known-tracks audio # Loop auidio
+shift+l no-osd seek 30 exact; script-message-to misc show-position # Seek exactly 1 second forward
+shift+h no-osd seek -30 exact; script-message-to misc show-position # Seek exactly 1 second backward
+alt+h vf toggle hflip # Flip horizontally
+shift+k no-osd seek 300 exact; script-message-to misc show-position # Seek exactly 5 seconds forward
+shift+j no-osd seek -300 exact; script-message-to misc show-position # Seek exactly 5 seconds backward
+x playlist-next # Skip to the next file
+> playlist-next # Skip to the next file
+w playlist-prev # Skip to the previous file
+< playlist-prev # Skip to the previous file
+WHEEL_DOWN add volume -5 # Volume 5 down
+down add volume -5 # Volume 5 down
+f add volume -5 # Volume 5 down
+WHEEL_UP add volume 5 # Volume 5 up
+up add volume 5 # Volume 5 up
+r add volume 5 # Volume 5 up
+ctrl+r script-binding reload/reload # Reload
+shift+r script-binding rename-file # Rename file
+alt+r script-binding history/history-resume # Resume history
+alt+shift+r script-binding history/play-recent # Play recent in history
+b cycle fullscreen # Toggle fullscreen
+shift+b script-message-to misc quick-bookmark # Bookmarks
+ctrl+t script-binding generate-thumbnails # Thumbnails
+alt+h script-message cycle_video_rotate -90 # Rotate -90
+alt+l script-message cycle_video_rotate 90 # Rotate 90
+g script-message contact-sheet-close; script-message playlist-view-toggle # Toggle gallery
+i script-message-to misc print-media-info # Toggle video info
+n set volume 50 # Set volume 50
+shift+n set speed 1.0 # Set normal speed 1.0
+m cycle mute # Toggle mute
+shift+m set speed 1.2 # Set default speed 1.2
+ctrl+m set volume 100 # Set volume 100
+shift+, multiply speed 1/1.1 # Speed down around .1
+shift+. multiply speed 1.1 # Speed up around .1
+F1 script-message-to command_palette show-command-palette "Command Palette" # Command Palette
+F2 script-message-to command_palette show-command-palette "Bindings" # Show bindings
+shift+F2 script-binding mdmenu/bindings # Binding list
+F3 script-message-to command_palette show-command-palette "Commands" # Show commands
+F4 script-message-to command_palette show-command-palette "Properties" # Show properties
+F5 script-message-to command_palette show-command-palette "Options" # Show options
+F6 script-message-to command_palette show-command-palette "Chapters" # Show chapters
+shift+F6 script-binding mdmenu/chapters # Chapter list
+F7 script-message-to command_palette show-command-palette "Playlist" # Show playlist
+shift+F7 script-binding mdmenu/playlist # Playlist
+F8 script-message-to command_palette show-command-palette "Video Tracks" # Show video tracks
+shift+F8 script-binding mdmenu/tracklist # Track list
+F9 script-message-to command_palette show-command-palette "Subtitle Tracks" # Show subtitle tracks
+F10 script-message-to command_palette show-command-palette "Profiles" # Show profiles
+F11 script-message-to command_palette show-command-palette "Audio Tracks" # Show audio tracks
+F12 script-message-to command_palette show-command-palette "Audio Devices" # Show audio devices
+ctrl+f script-message-to subtitle_search start-search # Search subtitle
+ctrl+shift+f script-message-to subtitle_search show-all-lines # Search all lines of subtitle
+shift+s script-message-to misc cycle-known-tracks audio # Cycle audio tracks
+ctrl+s script-message-to misc cycle-known-tracks sub up # Cycle subtitle track up
+ctrl+shift+s script-message-to misc cycle-known-tracks sub down # Cycle subtitle track down
+/ script-binding yt # Search yt
+? script-binding youtube-search # Search Youtube video
+TAB script-binding visibility # Toggle progress bar
+1 seek 10 absolute-percent # Seek 10% of a video
+2 seek 20 absolute-percent # Seek 20% of a video
+3 seek 30 absolute-percent # Seek 30% of a video
+4 seek 40 absolute-percent # Seek 40% of a video
+5 seek 50 absolute-percent # Seek 50% of a video
+6 seek 60 absolute-percent # Seek 60% of a video
+7 seek 70 absolute-percent # Seek 70% of a video
+8 seek 80 absolute-percent # Seek 80% of a video
+9 seek 90 absolute-percent # Seek 90% of a video
+0 seek 0 absolute-percent # Seek 0% of a video
+alt+1 set current-window-scale 0.5 # Halve the window size
+alt+2 set current-window-scale 1.0 # Reset the window size
+alt+3 set current-window-scale 2.0 # Double the window size
+- seek 99 absolute-percent # Seek 99% of a video
++ seek 99.9 absolute-percent # Seek 99.9% of a video
+[ script-binding UndoRedo/undo # Jump to previous position
+{ script-binding UndoRedo/undoCaps # Jump to previous position
+] script-binding UndoRedo/redo # Jump to undo position
+} script-binding UndoRedo/redoCaps # Jump to undo position
diff --git a/ar/.config/mpv/lua_settings/blur_edges.conf b/ar/.config/mpv/lua_settings/blur_edges.conf
new file mode 100644
index 0000000..b69ce99
--- /dev/null
+++ b/ar/.config/mpv/lua_settings/blur_edges.conf
@@ -0,0 +1,26 @@
+# whether the script is active by default
+active=yes
+
+# which black bars to replace with blur
+# can be "all", "horizontal" or "vertical"
+mode=all
+
+# intensity of the blur
+# see the ffmpeg filter doc https://ffmpeg.org/ffmpeg-filters.html#boxblur
+# tl;dr higher means blurrier
+blur_radius=10
+blur_power=10
+
+# the minimum size of the black bars for the effect to apply
+minimum_black_bar_size=3
+
+# if the aspect ratio of the video changes, we need to reapply the filter
+# since this can happen very quickly, wait a short delay before doing it
+reapply_delay=0.5
+
+# until recently, resuming files that had the script active would unselect the video
+# if your mpv version is more recent than feb 2 2018, you can set this to yes
+watch_later_fix=no
+
+# only apply the blur effect when mpv is set to fullscreen
+only_fullscreen=yes
diff --git a/ar/.config/mpv/lua_settings/gallery_worker.conf b/ar/.config/mpv/lua_settings/gallery_worker.conf
new file mode 100644
index 0000000..d56ab00
--- /dev/null
+++ b/ar/.config/mpv/lua_settings/gallery_worker.conf
@@ -0,0 +1,18 @@
+# mpv-gallery-view | https://github.com/occivink/mpv-gallery-view
+# This is the settings file for scripts/gallery-thumbgen.lua and its copies
+# File placement: script-opts/gallery_worker.conf
+# Defaults: https://github.com/occivink/mpv-gallery-view/blob/master/script-opts/gallery_worker.conf
+
+# accepts a |-separated list of URL patterns which gallery.lua should thumbnail using youtube-dl.
+# The patterns are matched after the http(s):// part of the URL.
+#^ matches the beginning of the URL, $ matches its end, and you should use % before any of the characters ^$()%|,.[]*+-? to match that character.
+#
+#Examples
+# will exclude any URL that starts with http://youtube.com or https://youtube.com:
+# ytdl_exclude=^youtube%.com
+# will exclude any URL that ends with .mkv or .mp4:
+# ytdl_exclude=%.mkv$|%.mp4$
+# See more lua patterns here: https://www.lua.org/manual/5.1/manual.html#5.4.1
+#
+#See also: ytdl_hook-exclude in mpv's manpage.
+ytdl_exclude=
diff --git a/ar/.config/mpv/lua_settings/mpv_crop_script.conf b/ar/.config/mpv/lua_settings/mpv_crop_script.conf
new file mode 100644
index 0000000..747b92e
--- /dev/null
+++ b/ar/.config/mpv/lua_settings/mpv_crop_script.conf
@@ -0,0 +1,2 @@
+output_template=/home/si/Picture/mpv_screenshots/${filename} ${#pos:%02h.%02m.%06.3s} ${!full:${crop_w}x${crop_h} ${%unique:%03d}}.png
+disable_keybind=yes
diff --git a/ar/.config/mpv/lua_settings/mpv_thumbnail_script.conf b/ar/.config/mpv/lua_settings/mpv_thumbnail_script.conf
new file mode 100644
index 0000000..5c82c2d
--- /dev/null
+++ b/ar/.config/mpv/lua_settings/mpv_thumbnail_script.conf
@@ -0,0 +1,3 @@
+prefer_mpv=no
+disable_keybinds=yes
+autogenerate_max_duration=21600
diff --git a/ar/.config/mpv/lua_settings/playlist_view.conf b/ar/.config/mpv/lua_settings/playlist_view.conf
new file mode 100644
index 0000000..777677c
--- /dev/null
+++ b/ar/.config/mpv/lua_settings/playlist_view.conf
@@ -0,0 +1,123 @@
+# mpv-gallery-view | https://github.com/occivink/mpv-gallery-view
+# This is the settings file for scripts/playlist-view.lua
+# File placement: script-opts/playlist_view.conf
+# Defaults: https://github.com/occivink/mpv-gallery-view/blob/master/script-opts/playlist_view.conf
+
+# thumbnail directory in which to create and look for thumbnails
+# on Unix-like platforms:
+#thumbs_dir=~/.cache/thumbnails/mpv-gallery
+# on Windows:
+#thumbs_dir=%APPDATA%\mpv\gallery-thumbs-dir
+# note that not all env vars get expanded, only '~' and 'APPDATA' do
+
+# create thumbs_dir if it doesn't exist
+# mkdir_thumbs=yes
+
+# use mpv instead of ffmpeg for thumbnail generation
+# slightly slower and does not support transparency, but does not require additional ffmpeg/ffprobe executables
+# yes on Windows, no on other plateforms
+#generate_thumbnails_with_mpv=no
+
+# all options below are platform-independent
+
+# fine-grained controls for the geometry of the gallery
+# each option can have a fixed value, or dynamic by using the following variables:
+# ww, wh: mpv window width, mpv window height (always available)
+# gx, gy: gallery horizontal position, gallery vertical position
+# gw, gh: gallery width, gallery height
+# sw, sh: minimum spacing width, minimum spacing height
+# tw, th: thumbnail width, thumbnail height
+# these strings are interpreted using the lua equivalent of "eval" so math functions and logical conditions can be used
+# if an option references variables, they will be computed in the appropriate order
+# (for example, if gallery_width == 5 * thumbnail_width, thumbnail_size will be computed before gallery_size)
+# in case of cyclical dependencies, the script will abort
+# example
+# -------
+# make the gallery centered
+gallery_position={ (ww - gw) / 2, (wh - gh) / 2 }
+# make the gallery's size 9/10 the size of the window
+gallery_size={ 9 * ww / 10, 9 * wh / 10 }
+# with at least 15 pixels of spacing between each thumbnail
+min_spacing={ 15, 15 }
+# and two thumbnail size presets for Windows smaller/bigger than 1366 x 768
+thumbnail_size=(ww * wh <= 1366 * 768) and {192, 108} or {288, 162}
+# it is recommended to use discrete increments for thumbnail_size since a new thumbnail needs to be generated for each size
+
+# limit the number of thumbnails visible, even if more could be shown
+# 64 is the maximum due to limitations in mpv
+max_thumbnails=64
+
+# the position in the file at which to take the thumbnail
+# can either be a percentage of the video duration, or a number of seconds
+take_thumbnail_at=20%
+
+# load to the selected video when the playlist-view is toggled off
+load_file_on_toggle_off=no
+# close the playlist-view when loading a video
+close_on_load_file=yes
+# pause the current video when the playlist-view is opened
+pause_on_start=yes
+# resume the current video when the playlist-view is closed
+# can be yes, no, or only-if-did-pause
+# in the latter case, will only resume if the video was actually paused by opening the playlist-view
+resume_on_stop=only-if-did-pause
+# automatically start the playlist-view when mpv is started
+start_on_mpv_startup=no
+# automatically start the playlist-view when the current file is finished
+# only has an effect when keep-open=always
+start_on_file_end=yes
+# if the currently playing file changes, set the selection to the new one
+follow_playlist_position=no
+# when loading a file, remember the time-position of the previous
+# and restart from there if it's loaded again
+remember_time_position=yes
+
+# show the filename below each thumbnail
+show_text=yes
+# use the playlist title if it exists instead of the filename
+show_title=yes
+strip_directory=yes
+strip_extension=yes
+text_size=28
+
+# colors are defined in hexadecimal in Blue Green Red (BGR) order
+# if multiple colors should be active, they get evenly blended
+# opacity is defined between 00 (opaque) and FF (transparent)
+background_color=333333
+background_opacity=33
+normal_border_color=BBBBBB
+normal_border_size=1
+selected_border_color=E5E4E5
+selected_border_size=6
+# show a special border around the currently playing file
+highlight_active=yes
+active_border_color=EBC5A7
+active_border_size=4
+flagged_border_color=96B58D
+flagged_border_size=4
+placeholder_color=222222
+
+# arbitrary commands that are run when the playlist-view is opened/closed
+# this can be used for lowering video settings when the gallery is active, since
+# high-quality video settings can result in slowdown of the gallery
+command_on_open=
+command_on_close=
+
+# the path of the 'flags' file that is written when you exit mpv
+flagged_file_path=./mpv_gallery_flagged
+
+mouse_support=yes
+UP=k
+DOWN=j
+LEFT=h
+RIGHT=l
+PAGE_UP=ctrl+u
+PAGE_DOWN=ctrl+d
+FIRST=0
+LAST=$
+RANDOM=r
+ACCEPT=ENTER
+CANCEL=ESC
+# this only removes entries from the playlist, not the underlying file
+REMOVE=BS
+FLAG=SPACE
diff --git a/ar/.config/mpv/mpv.conf b/ar/.config/mpv/mpv.conf
new file mode 100644
index 0000000..c38da51
--- /dev/null
+++ b/ar/.config/mpv/mpv.conf
@@ -0,0 +1,11 @@
+--script-opts=osc-scalewindowed=0.75,osc-scalefullscreen=0.75,osc-boxalpha=200,osc-visibility=always,osc-seekbarstyle=knob,osc-seekbarhandlesize=0.3,osc-seekbarkeyframes=no
+quiet=yes
+screenshot-directory="~/Pictures/screenshots"
+screenshot-template="%F-%P"
+sub-file-paths=Media/**
+audio-file-paths=Music/**
+--no-input-builtin-bindings
+--loop-playlist=inf
+# --speed=1.2
+--volume=0
+sub-font-provider=fontconfig
diff --git a/ar/.config/mpv/osc.conf b/ar/.config/mpv/osc.conf
new file mode 100644
index 0000000..68f6a4e
--- /dev/null
+++ b/ar/.config/mpv/osc.conf
@@ -0,0 +1 @@
+--script-opts=visibility=always
diff --git a/ar/.config/mpv/script-modules/extended-menu.lua b/ar/.config/mpv/script-modules/extended-menu.lua
new file mode 100644
index 0000000..b663e93
--- /dev/null
+++ b/ar/.config/mpv/script-modules/extended-menu.lua
@@ -0,0 +1,1214 @@
+local mp = require("mp")
+local utils = require("mp.utils")
+local assdraw = require("mp.assdraw")
+
+-- create namespace with default values
+local em = {
+
+ -- customisable values ------------------------------------------------------
+
+ loop_when_navigating = false, -- Loop when navigating through list
+ lines_to_show = 17, -- NOT including search line
+ pause_on_open = true,
+ resume_on_exit = "only-if-was-paused", -- another possible value is true
+
+ -- styles (earlyer it was a table, but required many more steps to pass def-s
+ -- here from .conf file)
+ font_size = 21,
+ --font size scales by window
+ scale_by_window = false,
+ -- cursor 'width', useful to change if you have hidpi monitor
+ cursor_x_border = 0.3,
+ line_bottom_margin = 1, -- basically space between lines
+ text_color = {
+ default = "ffffff",
+ accent = "d8a07b",
+ current = "aaaaaa",
+ comment = "636363",
+ },
+ menu_x_padding = 5, -- this padding for now applies only to 'left', not x
+ menu_y_padding = 2, -- but this one applies to both - top & bottom
+
+ -- values that should be passed from main script ----------------------------
+
+ search_heading = "Default search heading",
+ -- 'full' is required from main script, 'current_i' is optional
+ -- others are 'private'
+ list = {
+ full = {},
+ filtered = {},
+ current_i = nil,
+ pointer_i = 1,
+ show_from_to = {},
+ },
+ -- field to compare with when searching for 'current value' by 'current_i'
+ index_field = "index",
+ -- fields to use when searching for string match / any other custom searching
+ -- if value has 0 length, then search list item itself
+ filter_by_fields = {},
+
+ -- 'private' values that are not supposed to be changed from the outside ----
+
+ is_active = false,
+ -- https://mpv.io/manual/master/#lua-scripting-mp-create-osd-overlay(format)
+ ass = mp.create_osd_overlay("ass-events"),
+ was_paused = false, -- flag that indicates that vid was paused by this script
+
+ line = "",
+ -- if there was no cursor it wouldn't have been needed, but for now we need
+ -- variable below only to compare it with 'line' and see if we need to filter
+ prev_line = "",
+ cursor = 1,
+ history = {},
+ history_pos = 1,
+ key_bindings = {},
+ insert_mode = false,
+
+ -- used only in 'update' func to get error text msgs
+ error_codes = {
+ no_match = "Match required",
+ no_submit_provided = "No submit function provided",
+ },
+}
+
+-- PRIVATE METHODS ------------------------------------------------------------
+
+-- declare constructor function
+function em:new(o)
+ o = o or {}
+ setmetatable(o, self)
+ self.__index = self
+
+ -- some options might be customised by user in .conf file and read as strings
+ -- in that case parse those
+ if type(o.filter_by_fields) == "string" then
+ o.filter_by_fields = utils.parse_json(o.filter_by_fields)
+ end
+
+ if type(o.text_color) == "string" then
+ o.text_color = utils.parse_json(o.text_color)
+ end
+
+ return o
+end
+
+-- this func is just a getter of a current list depending on search line
+function em:current()
+ return self.line == "" and self.list.full or self.list.filtered
+end
+
+-- REVIEW: how to get rid of this wrapper and handle filter func sideeffects
+-- in a more elegant way?
+function em:filter_wrapper()
+ -- handles sideeffect that are needed to be run on filtering list
+ -- cuz the filter func may be redefined in main script and therefore needs
+ -- to be straight forward - only doing filtering and returning the table
+
+ -- passing current query just in case, so ppl can use it in their custom funcs
+ self.list.filtered = self:filter(self.line)
+
+ self.prev_line = self.line
+ self.list.pointer_i = 1
+ self:set_from_to(true)
+end
+
+function em:set_from_to(reset_flag)
+ -- additional variables just for shorter var name
+ local i = self.list.pointer_i
+ local to_show = self.lines_to_show
+ local total = #self:current()
+
+ if reset_flag or to_show >= total then
+ self.list.show_from_to = { 1, math.min(to_show, total) }
+ return
+ end
+
+ -- If menu is opened with something already selected we want this 'selected'
+ -- to be displayed close to the middle of the menu. That's why 'show_from_to'
+ -- is not initially set, so we can know - if show_from_to length is 0 - it is
+ -- first call of this func in cur. init
+ if #self.list.show_from_to == 0 then
+ -- set show_from_to so chosen item will be displayed close to middle
+ local half_list = math.ceil(to_show / 2)
+ if i < half_list then
+ self.list.show_from_to = { 1, to_show }
+ elseif total - i < half_list then
+ self.list.show_from_to = { total - to_show + 1, total }
+ else
+ self.list.show_from_to = { i - half_list + 1, i - half_list + to_show }
+ end
+ else
+ table.unpack = table.unpack or unpack -- 5.1 compatibility
+ local first, last = table.unpack(self.list.show_from_to)
+
+ -- handle cursor moving towards start / end bondary
+ if first ~= 1 and i - first < 2 then
+ self.list.show_from_to = { first - 1, last - 1 }
+ end
+ if last ~= total and last - i < 2 then
+ self.list.show_from_to = { first + 1, last + 1 }
+ end
+
+ -- handle index jumps from beginning to end and backwards
+ if i > last then
+ self.list.show_from_to = { i - to_show + 1, i }
+ end
+ if i < first then
+ self.list.show_from_to = { 1, to_show }
+ end
+ end
+end
+
+function em:change_selected_index(num)
+ self.list.pointer_i = self.list.pointer_i + num
+ if self.loop_when_navigating then
+ if self.list.pointer_i < 1 then
+ self.list.pointer_i = #self:current()
+ elseif self.list.pointer_i > #self:current() then
+ self.list.pointer_i = 1
+ end
+ else
+ if self.list.pointer_i < 1 then
+ self.list.pointer_i = 1
+ elseif self.list.pointer_i > #self:current() then
+ self.list.pointer_i = #self:current()
+ end
+ end
+ self:set_from_to()
+ self:update()
+end
+
+-- Render the REPL and console as an ASS OSD
+function em:update(err_code)
+ -- ASS tags documentation here - https://aegi.vmoe.info/docs/3.0/ASS_Tags/
+
+ -- do not bother if function was called to close the menu..
+ if not self.is_active then
+ em.ass:remove()
+ return
+ end
+
+ local line_height = self.font_size + self.line_bottom_margin
+ local _, h, aspect = mp.get_osd_size()
+ local wh = self.scale_by_window and 720 or h
+ local ww = wh * aspect
+
+ -- '+ 1' below is a search string
+ local menu_y_pos = wh - (line_height * (self.lines_to_show + 1) + self.menu_y_padding * 2)
+
+ -- didn't find better place to handle filtered list update
+ if self.line ~= self.prev_line then
+ self:filter_wrapper()
+ end
+
+ local function get_background()
+ local a = self:ass_new_wrapper()
+ a:append("{\\1c&H1c1c1c\\1a&H19}") -- background color & opacity
+ a:pos(0, 0)
+ a:draw_start()
+ a:rect_cw(0, menu_y_pos, ww, wh)
+ a:draw_stop()
+ return a.text
+ end
+
+ local function get_search_header()
+ local a = self:ass_new_wrapper()
+
+ a:pos(self.menu_x_padding, menu_y_pos + self.menu_y_padding)
+
+ local search_prefix = table.concat({
+ self:get_font_color("accent"),
+ (#self:current() ~= 0 and self.list.pointer_i or "!"),
+ "/",
+ #self:current(),
+ "\\h\\h",
+ self.search_heading,
+ ":\\h",
+ })
+
+ a:append(search_prefix)
+ -- reset font color after search prefix
+ a:append(self:get_font_color("default"))
+
+ -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor
+ -- inline with the surrounding text, but it sets the advance to the width
+ -- of the drawing. So the cursor doesn't affect layout too much, make it as
+ -- thin as possible and make it appear to be 1px wide by giving it 0.5px
+ -- horizontal borders.
+ local cheight = self.font_size * 8
+ -- TODO: maybe do it using draw_rect from ass?
+ local cglyph = "{\\r" -- styles reset
+ .. "\\1c&Hffffff&\\3c&Hffffff" -- font color and border color
+ .. "\\xbord"
+ .. self.cursor_x_border
+ .. "\\p4\\pbo24}" -- xborder, scale x8 and baseline offset
+ .. "m 0 0 l 0 "
+ .. cheight -- drawing just a line
+ .. "{\\p0\\r}" -- finish drawing and reset styles
+ local before_cur = self:ass_escape(self.line:sub(1, self.cursor - 1))
+ local after_cur = self:ass_escape(self.line:sub(self.cursor))
+
+ a:append(table.concat({
+ before_cur,
+ cglyph,
+ self:reset_styles(),
+ self:get_font_color("default"),
+ after_cur,
+ (err_code and "\\h" .. self.error_codes[err_code] or ""),
+ }))
+
+ return a.text
+
+ -- NOTE: perhaps this commented code will some day help me in coding cursor
+ -- like in M-x emacs menu:
+ -- Redraw the cursor with the REPL text invisible. This will make the
+ -- cursor appear in front of the text.
+ -- ass:new_event()
+ -- ass:an(1)
+ -- ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur)
+ -- ass:append(cglyph)
+ -- ass:append(style .. '{\\alpha&HFF&}' .. after_cur)
+ end
+
+ local function get_list()
+ local a = assdraw.ass_new()
+
+ local function apply_highlighting(y)
+ a:new_event()
+ a:append(self:reset_styles())
+ a:append("{\\1c&Hffffff\\1a&HE6}") -- background color & opacity
+ a:pos(0, 0)
+ a:draw_start()
+ a:rect_cw(0, y, ww, y + self.font_size)
+ a:draw_stop()
+ end
+
+ -- REVIEW: maybe make another function 'get_line_str' and move there
+ -- everything from this for loop?
+ -- REVIEW: how to use something like table.unpack below?
+ for i = self.list.show_from_to[1], self.list.show_from_to[2] do
+ local value = assert(self:current()[i], "no value with index " .. i)
+ local y_offset = menu_y_pos + self.menu_y_padding + (line_height * (i - self.list.show_from_to[1] + 1))
+
+ if i == self.list.pointer_i then
+ apply_highlighting(y_offset)
+ end
+
+ a:new_event()
+ a:append(self:reset_styles())
+ a:pos(self.menu_x_padding, y_offset)
+ a:append(self:get_line(i, value))
+ end
+
+ return a.text
+ end
+
+ em.ass.res_x = ww
+ em.ass.res_y = wh
+ em.ass.data = table.concat({
+ get_background(),
+ get_search_header(),
+ get_list(),
+ }, "\n")
+
+ em.ass:update()
+end
+
+-- params:
+-- - data : {list: {}, [current_i] : num}
+function em:init(data)
+ self.list.full = data.list or {}
+ self.list.current_i = data.current_i or nil
+ self.list.pointer_i = data.current_i or 1
+ self:set_active(true)
+end
+
+function em:exit()
+ self:undefine_key_bindings()
+ collectgarbage()
+end
+
+-- TODO: write some idle func like this
+-- function idle()
+-- if pending_selection then
+-- gallery:set_selection(pending_selection)
+-- pending_selection = nil
+-- end
+-- if ass_changed or geometry_changed then
+-- local ww, wh = mp.get_osd_size()
+-- if geometry_changed then
+-- geometry_changed = false
+-- compute_geometry(ww, wh)
+-- end
+-- if ass_changed then
+-- ass_changed = false
+-- mp.set_osd_ass(ww, wh, ass)
+-- end
+-- end
+-- end
+-- ...
+-- and handle it as follows
+-- init():
+-- mp.register_idle(idle)
+-- idle()
+-- exit():
+-- mp.unregister_idle(idle)
+-- idle()
+-- And in these observers he is setting a flag, that's being checked in func above
+-- mp.observe_property("osd-width", "native", mark_geometry_stale)
+-- mp.observe_property("osd-height", "native", mark_geometry_stale)
+
+-- PRIVATE METHODS END --------------------------------------------------------
+
+-- PUBLIC METHODS -------------------------------------------------------------
+
+function em:filter()
+ -- default filter func, might be redefined in main script
+ local result = {}
+
+ local function get_full_search_str(v)
+ local str = ""
+ for _, key in ipairs(self.filter_by_fields) do
+ str = str .. (v[key] or "")
+ end
+ return str
+ end
+
+ for _, v in ipairs(self.list.full) do
+ -- if filter_by_fields has 0 length, then search list item itself
+ if #self.filter_by_fields == 0 then
+ if self:search_method(v) then
+ table.insert(result, v)
+ end
+ else
+ -- NOTE: we might use search_method on fiels separately like this:
+ -- for _,key in ipairs(self.filter_by_fields) do
+ -- if self:search_method(v[key]) then table.insert(result, v) end
+ -- end
+ -- But since im planning to implement fuzzy search in future i need full
+ -- search string here
+ if self:search_method(get_full_search_str(v)) then
+ table.insert(result, v)
+ end
+ end
+ end
+ return result
+end
+
+-- TODO: implement fuzzy search and maybe match highlights
+function em:search_method(str)
+ -- also might be redefined by main script
+
+ -- convert to string just to make sure..
+ return tostring(str):lower():find(self.line:lower(), 1, true)
+end
+
+-- this module requires submit function to be defined in main script
+function em:submit()
+ self:update("no_submit_provided")
+end
+
+function em:update_list(list)
+ -- for now this func doesn't handle cases when we have 'current_i' to update
+ -- it
+ self.list.full = list
+ if self.line ~= self.prev_line then
+ self:filter_wrapper()
+ end
+end
+
+-- PUBLIC METHODS END ---------------------------------------------------------
+
+-- HELPER METHODS -------------------------------------------------------------
+
+function em:get_line(_, v) -- [i]ndex, [v]alue
+ -- this func might be redefined in main script to get a custom-formatted line
+ -- default implementation of this func supposes that value.content field is a
+ -- String
+ local a = assdraw.ass_new()
+ local style = (self.list.current_i == v[self.index_field]) and "current" or "default"
+
+ a:append(self:reset_styles())
+ a:append(self:get_font_color(style))
+ -- content as default field, which is holding string
+ -- no point in moving it to main object since content itself is being
+ -- composed in THIS function, that might (and most likely, should) be
+ -- redefined in main script
+ a:append(v.content or "Something is off in `get_line` func")
+ return a.text
+end
+
+-- REVIEW: for now i don't see normal way of mergin this func with below one
+-- but it's being used only once
+function em:reset_styles()
+ local a = assdraw.ass_new()
+ -- alignment top left, no word wrapping, border 0, shadow 0
+ a:append("{\\an7\\q2\\bord0\\shad0}")
+ a:append("{\\fs" .. self.font_size .. "}")
+ return a.text
+end
+
+-- function to get rid of some copypaste
+function em:ass_new_wrapper()
+ local a = assdraw.ass_new()
+ a:new_event()
+ a:append(self:reset_styles())
+ return a
+end
+
+function em:get_font_color(style)
+ return "{\\1c&H" .. self.text_color[style] .. "}"
+end
+
+-- HELPER METHODS END ---------------------------------------------------------
+
+--[[
+ The below code is a modified implementation of text input from mpv's console.lua:
+ https://github.com/mpv-player/mpv/blob/87c9eefb2928252497f6141e847b74ad1158bc61/player/lua/console.lua
+
+ I was too lazy to list all modifications i've done to the script, but if u
+ rly need to see those - do diff with the original code
+]]
+--
+
+-------------------------------------------------------------------------------
+-- START ORIGINAL MPV CODE --
+-------------------------------------------------------------------------------
+
+-- Copyright (C) 2019 the mpv developers
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+function em:detect_platform()
+ local o = {}
+ -- Kind of a dumb way of detecting the platform but whatever
+ if mp.get_property_native("options/vo-mmcss-profile", o) ~= o then
+ return "windows"
+ elseif mp.get_property_native("options/macos-force-dedicated-gpu", o) ~= o then
+ return "macos"
+ elseif os.getenv("WAYLAND_DISPLAY") then
+ return "wayland"
+ end
+ return "x11"
+end
+
+-- Escape a string for verbatim display on the OSD
+function em:ass_escape(str)
+ -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
+ -- it isn't followed by a recognised character, so add a zero-width
+ -- non-breaking space
+ str = str:gsub("\\", "\\\239\187\191")
+ str = str:gsub("{", "\\{")
+ str = str:gsub("}", "\\}")
+ -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
+ -- consecutive newlines
+ str = str:gsub("\n", "\239\187\191\\N")
+ -- Turn leading spaces into hard spaces to prevent ASS from stripping them
+ str = str:gsub("\\N ", "\\N\\h")
+ str = str:gsub("^ ", "\\h")
+ return str
+end
+
+-- Set the REPL visibility ("enable", Esc)
+function em:set_active(active)
+ if active == self.is_active then
+ return
+ end
+ if active then
+ self.is_active = true
+ self.insert_mode = false
+ mp.enable_messages("terminal-default")
+ self:define_key_bindings()
+
+ -- set flag 'was_paused' only if vid wasn't paused before EM init
+ if self.pause_on_open and not mp.get_property_bool("pause", false) then
+ mp.set_property_bool("pause", true)
+ self.was_paused = true
+ end
+
+ self:set_from_to()
+ self:update()
+ else
+ -- no need to call 'update' in this block cuz 'clear' method is calling it
+ self.is_active = false
+ self:undefine_key_bindings()
+
+ if self.resume_on_exit == true or (self.resume_on_exit == "only-if-was-paused" and self.was_paused) then
+ mp.set_property_bool("pause", false)
+ end
+
+ self:clear()
+ collectgarbage()
+ end
+end
+
+-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
+-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
+function em:next_utf8(str, pos)
+ if pos > str:len() then
+ return pos
+ end
+ repeat
+ pos = pos + 1
+ until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
+
+-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
+function em:prev_utf8(str, pos)
+ if pos <= 1 then
+ return pos
+ end
+ repeat
+ pos = pos - 1
+ until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
+
+-- Insert a character at the current cursor position (any_unicode)
+function em:handle_char_input(c)
+ if self.insert_mode then
+ self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self:next_utf8(self.line, self.cursor))
+ else
+ self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self.cursor)
+ end
+ self.cursor = self.cursor + #c
+ self:update()
+end
+
+-- Remove the character behind the cursor (Backspace)
+function em:handle_backspace()
+ if self.cursor <= 1 then
+ return
+ end
+ local prev = self:prev_utf8(self.line, self.cursor)
+ self.line = self.line:sub(1, prev - 1) .. self.line:sub(self.cursor)
+ self.cursor = prev
+ self:update()
+end
+
+-- Remove the character in front of the cursor (Del)
+function em:handle_del()
+ if self.cursor > self.line:len() then
+ return
+ end
+ self.line = self.line:sub(1, self.cursor - 1) .. self.line:sub(self:next_utf8(self.line, self.cursor))
+ self:update()
+end
+
+-- Toggle insert mode (Ins)
+function em:handle_ins()
+ self.insert_mode = not self.insert_mode
+end
+
+-- Move the cursor to the next character (Right)
+function em:next_char()
+ self.cursor = self:next_utf8(self.line, self.cursor)
+ self:update()
+end
+
+-- Move the cursor to the previous character (Left)
+function em:prev_char()
+ self.cursor = self:prev_utf8(self.line, self.cursor)
+ self:update()
+end
+
+-- Clear the current line (Ctrl+C)
+function em:clear()
+ self.line = ""
+ self.prev_line = ""
+
+ self.list.current_i = nil
+ self.list.pointer_i = 1
+ self.list.filtered = {}
+ self.list.show_from_to = {}
+
+ self.was_paused = false
+
+ self.cursor = 1
+ self.insert_mode = false
+ self.history_pos = #self.history + 1
+
+ self:update()
+end
+
+-- Run the current command and clear the line (Enter)
+function em:handle_enter()
+ if #self:current() == 0 then
+ self:update("no_match")
+ return
+ end
+
+ if self.history[#self.history] ~= self.line then
+ self.history[#self.history + 1] = self.line
+ end
+
+ self:submit(self:current()[self.list.pointer_i])
+ self:set_active(false)
+end
+
+-- Go to the specified position in the command history
+function em:go_history(new_pos)
+ local old_pos = self.history_pos
+ self.history_pos = new_pos
+
+ -- Restrict the position to a legal value
+ if self.history_pos > #self.history + 1 then
+ self.history_pos = #self.history + 1
+ elseif self.history_pos < 1 then
+ self.history_pos = 1
+ end
+
+ -- Do nothing if the history position didn't actually change
+ if self.history_pos == old_pos then
+ return
+ end
+
+ -- If the user was editing a non-history line, save it as the last history
+ -- entry. This makes it much less frustrating to accidentally hit Up/Down
+ -- while editing a line.
+ if old_pos == #self.history + 1 and self.line ~= "" and self.history[#self.history] ~= self.line then
+ self.history[#self.history + 1] = self.line
+ end
+
+ -- Now show the history line (or a blank line for #history + 1)
+ if self.history_pos <= #self.history then
+ self.line = self.history[self.history_pos]
+ else
+ self.line = ""
+ end
+ self.cursor = self.line:len() + 1
+ self.insert_mode = false
+ self:update()
+end
+
+-- Go to the specified relative position in the command history (Up, Down)
+function em:move_history(amount)
+ self:go_history(self.history_pos + amount)
+end
+
+-- Go to the first command in the command history (PgUp)
+function em:handle_pgup()
+ -- Determine the number of items to move up (half a page)
+ local half_page = math.ceil(self.lines_to_show / 2)
+
+ -- Move the history position up by half a page
+ self:change_selected_index(-half_page)
+end
+
+-- Stop browsing history and start editing a blank line (PgDown)
+function em:handle_pgdown()
+ -- Determine the number of items to move down (half a page)
+ local half_page = math.ceil(self.lines_to_show / 2)
+
+ -- Move the history position down by half a page
+ self:change_selected_index(half_page)
+end
+
+-- Move to the start of the current word, or if already at the start, the start
+-- of the previous word. (Ctrl+Left)
+function em:prev_word()
+ -- This is basically the same as next_word() but backwards, so reverse the
+ -- string in order to do a "backwards" find. This wouldn't be as annoying
+ -- to do if Lua didn't insist on 1-based indexing.
+ self.cursor = self.line:len()
+ - select(2, self.line:reverse():find("%s*[^%s]*", self.line:len() - self.cursor + 2))
+ + 1
+ self:update()
+end
+
+-- Move to the end of the current word, or if already at the end, the end of
+-- the next word. (Ctrl+Right)
+function em:next_word()
+ self.cursor = select(2, self.line:find("%s*[^%s]*", self.cursor)) + 1
+ self:update()
+end
+
+-- Move the cursor to the beginning of the line (HOME)
+function em:go_home()
+ self.cursor = 1
+ self:update()
+end
+
+-- Move the cursor to the end of the line (END)
+function em:go_end()
+ self.cursor = self.line:len() + 1
+ self:update()
+end
+
+-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
+function em:del_word()
+ local before_cur = self.line:sub(1, self.cursor - 1)
+ local after_cur = self.line:sub(self.cursor)
+
+ before_cur = before_cur:gsub("[^%s]+%s*$", "", 1)
+ self.line = before_cur .. after_cur
+ self.cursor = before_cur:len() + 1
+ self:update()
+end
+
+-- Delete from the cursor to the end of the word (Ctrl+Del)
+function em:del_next_word()
+ if self.cursor > self.line:len() then
+ return
+ end
+
+ local before_cur = self.line:sub(1, self.cursor - 1)
+ local after_cur = self.line:sub(self.cursor)
+
+ after_cur = after_cur:gsub("^%s*[^%s]+", "", 1)
+ self.line = before_cur .. after_cur
+ self:update()
+end
+
+-- Delete from the cursor to the end of the line (Ctrl+K)
+function em:del_to_eol()
+ self.line = self.line:sub(1, self.cursor - 1)
+ self:update()
+end
+
+-- Delete from the cursor back to the start of the line (Ctrl+U)
+function em:del_to_start()
+ self.line = self.line:sub(self.cursor)
+ self.cursor = 1
+ self:update()
+end
+
+-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
+function em:get_clipboard(clip)
+ -- Pick a better default font for Windows and macOS
+ local platform = self:detect_platform()
+
+ if platform == "x11" then
+ local res = utils.subprocess({
+ args = { "xclip", "-selection", clip and "clipboard" or "primary", "-out" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "wayland" then
+ local res = utils.subprocess({
+ args = { "wl-paste", clip and "-n" or "-np" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "windows" then
+ local res = utils.subprocess({
+ args = {
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ [[& {
+ Trap {
+ Write-Error -ErrorRecord $_
+ Exit 1
+ }
+
+ $clip = ""
+ if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
+ $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
+ } else {
+ Add-Type -AssemblyName PresentationCore
+ $clip = [Windows.Clipboard]::GetText()
+ }
+
+ $clip = $clip -Replace "`r",""
+ $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
+ [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
+ }]],
+ },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "macos" then
+ local res = utils.subprocess({
+ args = { "pbpaste" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ end
+ return ""
+end
+
+-- Paste text from the window-system's clipboard. 'clip' determines whether the
+-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
+function em:paste(clip)
+ local text = self:get_clipboard(clip)
+ local before_cur = self.line:sub(1, self.cursor - 1)
+ local after_cur = self.line:sub(self.cursor)
+ self.line = before_cur .. text .. after_cur
+ self.cursor = self.cursor + text:len()
+ self:update()
+end
+
+-- List of input bindings. This is a weird mashup between common GUI text-input
+-- bindings and readline bindings.
+function em:get_bindings()
+ local bindings = {
+ {
+ "ctrl+[",
+ function()
+ self:set_active(false)
+ end,
+ },
+ {
+ "ctrl+g",
+ function()
+ self:set_active(false)
+ end,
+ },
+ {
+ "esc",
+ function()
+ self:set_active(false)
+ end,
+ },
+ {
+ "enter",
+ function()
+ self:handle_enter()
+ end,
+ },
+ {
+ "kp_enter",
+ function()
+ self:handle_enter()
+ end,
+ },
+ {
+ "ctrl+m",
+ function()
+ self:handle_enter()
+ end,
+ },
+ {
+ "bs",
+ function()
+ self:handle_backspace()
+ end,
+ },
+ {
+ "shift+bs",
+ function()
+ self:handle_backspace()
+ end,
+ },
+ {
+ "ctrl+h",
+ function()
+ self:handle_backspace()
+ end,
+ },
+ {
+ "del",
+ function()
+ self:handle_del()
+ end,
+ },
+ {
+ "shift+del",
+ function()
+ self:handle_del()
+ end,
+ },
+ {
+ "ins",
+ function()
+ self:handle_ins()
+ end,
+ },
+ {
+ "shift+ins",
+ function()
+ self:paste(false)
+ end,
+ },
+ {
+ "mbtn_mid",
+ function()
+ self:paste(false)
+ end,
+ },
+ {
+ "left",
+ function()
+ self:prev_char()
+ end,
+ },
+ {
+ "ctrl+b",
+ function()
+ self:prev_char()
+ end,
+ },
+ {
+ "right",
+ function()
+ self:next_char()
+ end,
+ },
+ {
+ "ctrl+f",
+ function()
+ self:next_char()
+ end,
+ },
+ {
+ "ctrl+k",
+ function()
+ self:change_selected_index(-1)
+ end,
+ },
+ {
+ "ctrl+p",
+ function()
+ self:change_selected_index(-1)
+ end,
+ },
+ {
+ "ctrl+j",
+ function()
+ self:change_selected_index(1)
+ end,
+ },
+ {
+ "ctrl+n",
+ function()
+ self:change_selected_index(1)
+ end,
+ },
+ {
+ "up",
+ function()
+ self:move_history(-1)
+ end,
+ },
+ {
+ "alt+p",
+ function()
+ self:move_history(-1)
+ end,
+ },
+ {
+ "wheel_up",
+ function()
+ self:move_history(-1)
+ end,
+ },
+ {
+ "down",
+ function()
+ self:move_history(1)
+ end,
+ },
+ {
+ "alt+n",
+ function()
+ self:move_history(1)
+ end,
+ },
+ {
+ "wheel_down",
+ function()
+ self:move_history(1)
+ end,
+ },
+ { "wheel_left", function() end },
+ { "wheel_right", function() end },
+ {
+ "ctrl+left",
+ function()
+ self:prev_word()
+ end,
+ },
+ {
+ "alt+b",
+ function()
+ self:prev_word()
+ end,
+ },
+ {
+ "ctrl+right",
+ function()
+ self:next_word()
+ end,
+ },
+ {
+ "alt+f",
+ function()
+ self:next_word()
+ end,
+ },
+ {
+ "ctrl+a",
+ function()
+ self:go_home()
+ end,
+ },
+ {
+ "home",
+ function()
+ self:go_home()
+ end,
+ },
+ {
+ "ctrl+e",
+ function()
+ self:go_end()
+ end,
+ },
+ {
+ "end",
+ function()
+ self:go_end()
+ end,
+ },
+ {
+ "ctrl+shift+f",
+ function()
+ self:handle_pgdown()
+ end,
+ },
+ {
+ "ctrl+shift+b",
+ function()
+ self:handle_pgup()
+ end,
+ },
+ {
+ "pgdwn",
+ function()
+ self:handle_pgdown()
+ end,
+ },
+ {
+ "pgup",
+ function()
+ self:handle_pgup()
+ end,
+ },
+ {
+ "ctrl+c",
+ function()
+ self:clear()
+ end,
+ },
+ {
+ "ctrl+d",
+ function()
+ self:handle_del()
+ end,
+ },
+ {
+ "ctrl+u",
+ function()
+ self:del_to_start()
+ end,
+ },
+ {
+ "ctrl+v",
+ function()
+ self:paste(true)
+ end,
+ },
+ {
+ "meta+v",
+ function()
+ self:paste(true)
+ end,
+ },
+ {
+ "ctrl+bs",
+ function()
+ self:del_word()
+ end,
+ },
+ {
+ "ctrl+w",
+ function()
+ self:del_word()
+ end,
+ },
+ {
+ "ctrl+del",
+ function()
+ self:del_next_word()
+ end,
+ },
+ {
+ "alt+d",
+ function()
+ self:del_next_word()
+ end,
+ },
+ {
+ "kp_dec",
+ function()
+ self:handle_char_input(".")
+ end,
+ },
+ }
+
+ for i = 0, 9 do
+ bindings[#bindings + 1] = {
+ "kp" .. i,
+ function()
+ self:handle_char_input("" .. i)
+ end,
+ }
+ end
+
+ return bindings
+end
+
+function em:text_input(info)
+ if info.key_text and (info.event == "press" or info.event == "down" or info.event == "repeat") then
+ self:handle_char_input(info.key_text)
+ end
+end
+
+function em:define_key_bindings()
+ if #self.key_bindings > 0 then
+ return
+ end
+ for _, bind in ipairs(self:get_bindings()) do
+ -- Generate arbitrary name for removing the bindings later.
+ local name = "search_" .. (#self.key_bindings + 1)
+ self.key_bindings[#self.key_bindings + 1] = name
+ mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true })
+ end
+ mp.add_forced_key_binding("any_unicode", "search_input", function(...)
+ self:text_input(...)
+ end, { repeatable = true, complex = true })
+ self.key_bindings[#self.key_bindings + 1] = "search_input"
+end
+
+function em:undefine_key_bindings()
+ for _, name in ipairs(self.key_bindings) do
+ mp.remove_key_binding(name)
+ end
+ self.key_bindings = {}
+end
+
+-------------------------------------------------------------------------------
+-- END ORIGINAL MPV CODE --
+-------------------------------------------------------------------------------
+
+return em
diff --git a/ar/.config/mpv/script-modules/gallery.lua b/ar/.config/mpv/script-modules/gallery.lua
new file mode 100644
index 0000000..ee0be42
--- /dev/null
+++ b/ar/.config/mpv/script-modules/gallery.lua
@@ -0,0 +1,581 @@
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+local assdraw = require("mp.assdraw")
+
+local gallery_mt = {}
+gallery_mt.__index = gallery_mt
+
+function gallery_new()
+ local gallery = setmetatable({
+ -- public, can be modified by user
+ items = {},
+ item_to_overlay_path = function(index, item)
+ return ""
+ end,
+ item_to_thumbnail_params = function(index, item)
+ return "", 0
+ end,
+ item_to_text = function(index, item)
+ return "", true
+ end,
+ item_to_border = function(index, item)
+ return 0, ""
+ end,
+ ass_show = function(ass) end,
+ config = {
+ background_color = "333333",
+ background_opacity = "33",
+ background_roundness = 5,
+ scrollbar = true,
+ scrollbar_left_side = false,
+ scrollbar_min_size = 10,
+ overlay_range = 0,
+ max_thumbnails = 64,
+ show_placeholders = true,
+ always_show_placeholders = false,
+ placeholder_color = "222222",
+ text_size = 28,
+ align_text = true,
+ accurate = false,
+ generate_thumbnails_with_mpv = false,
+ },
+
+ -- private, can be read but should not be modified
+ active = false,
+ geometry = {
+ ok = false,
+ position = { 0, 0 },
+ size = { 0, 0 },
+ min_spacing = { 0, 0 },
+ thumbnail_size = { 0, 0 },
+ rows = 0,
+ columns = 0,
+ effective_spacing = { 0, 0 },
+ },
+ view = { -- 1-based indices into the "playlist" array
+ first = 0, -- must be equal to N*columns
+ last = 0, -- must be > first and <= first + rows*columns
+ },
+ overlays = {
+ active = {}, -- array of <=64 strings indicating the file associated to the current overlay (false if nothing)
+ missing = {}, -- associative array of thumbnail path to view index it should be shown at
+ },
+ selection = nil,
+ ass = {
+ background = "",
+ selection = "",
+ scrollbar = "",
+ placeholders = "",
+ },
+ generators = {}, -- list of generator scripts
+ }, gallery_mt)
+
+ for i = 1, gallery.config.max_thumbnails do
+ gallery.overlays.active[i] = false
+ end
+ return gallery
+end
+
+function gallery_mt.show_overlay(gallery, index_1, thumb_path)
+ local g = gallery.geometry
+ gallery.overlays.active[index_1] = thumb_path
+ local index_0 = index_1 - 1
+ local x, y = gallery:view_index_position(index_0)
+ mp.commandv(
+ "overlay-add",
+ tostring(index_0 + gallery.config.overlay_range),
+ tostring(math.floor(x + 0.5)),
+ tostring(math.floor(y + 0.5)),
+ thumb_path,
+ "0",
+ "bgra",
+ tostring(g.thumbnail_size[1]),
+ tostring(g.thumbnail_size[2]),
+ tostring(4 * g.thumbnail_size[1])
+ )
+ mp.osd_message(" ", 0.01)
+end
+
+function gallery_mt.remove_overlays(gallery)
+ for view_index, _ in pairs(gallery.overlays.active) do
+ mp.commandv("overlay-remove", gallery.config.overlay_range + view_index - 1)
+ gallery.overlays.active[view_index] = false
+ end
+ gallery.overlays.missing = {}
+end
+
+local function file_exists(path)
+ local info = utils.file_info(path)
+ return info ~= nil and info.is_file
+end
+
+function gallery_mt.refresh_overlays(gallery, force)
+ local todo = {}
+ local o = gallery.overlays
+ local g = gallery.geometry
+ o.missing = {}
+ for view_index = 1, g.rows * g.columns do
+ local index = gallery.view.first + view_index - 1
+ local active = o.active[view_index]
+ if index > 0 and index <= #gallery.items then
+ local thumb_path = gallery.item_to_overlay_path(index, gallery.items[index])
+ if not force and active == thumb_path then
+ -- nothing to do
+ elseif file_exists(thumb_path) then
+ gallery:show_overlay(view_index, thumb_path)
+ else
+ -- need to generate that thumbnail
+ o.active[view_index] = false
+ mp.commandv("overlay-remove", gallery.config.overlay_range + view_index - 1)
+ o.missing[thumb_path] = view_index
+ todo[#todo + 1] = { index = index, output = thumb_path }
+ end
+ else
+ -- might happen if we're close to the end of gallery.items
+ if active ~= false then
+ o.active[view_index] = false
+ mp.commandv("overlay-remove", gallery.config.overlay_range + view_index - 1)
+ end
+ end
+ end
+ if #gallery.generators >= 1 then
+ -- reverse iterate so that the first thumbnail is at the top of the stack
+ for i = #todo, 1, -1 do
+ local generator = gallery.generators[i % #gallery.generators + 1]
+ local t = todo[i]
+ local input_path, time = gallery.item_to_thumbnail_params(t.index, gallery.items[t.index])
+ mp.commandv(
+ "script-message-to",
+ generator,
+ "push-thumbnail-front",
+ mp.get_script_name(),
+ input_path,
+ tostring(g.thumbnail_size[1]),
+ tostring(g.thumbnail_size[2]),
+ time,
+ t.output,
+ gallery.config.accurate and "true" or "false",
+ gallery.config.generate_thumbnails_with_mpv and "true" or "false"
+ )
+ end
+ end
+end
+
+function gallery_mt.index_at(gallery, mx, my)
+ local g = gallery.geometry
+ if mx < g.position[1] or my < g.position[2] then
+ return nil
+ end
+ mx = mx - g.position[1]
+ my = my - g.position[2]
+ if mx > g.size[1] or my > g.size[2] then
+ return nil
+ end
+ mx = mx - g.effective_spacing[1]
+ my = my - g.effective_spacing[2]
+ local on_column = (mx % (g.thumbnail_size[1] + g.effective_spacing[1])) < g.thumbnail_size[1]
+ local on_row = (my % (g.thumbnail_size[2] + g.effective_spacing[2])) < g.thumbnail_size[2]
+ if on_column and on_row then
+ local column = math.floor(mx / (g.thumbnail_size[1] + g.effective_spacing[1]))
+ local row = math.floor(my / (g.thumbnail_size[2] + g.effective_spacing[2]))
+ local index = gallery.view.first + row * g.columns + column
+ if index > 0 and index <= gallery.view.last then
+ return index
+ end
+ end
+ return nil
+end
+
+function gallery_mt.compute_internal_geometry(gallery)
+ local g = gallery.geometry
+ g.rows = math.floor((g.size[2] - g.min_spacing[2]) / (g.thumbnail_size[2] + g.min_spacing[2]))
+ g.columns = math.floor((g.size[1] - g.min_spacing[1]) / (g.thumbnail_size[1] + g.min_spacing[1]))
+ if g.rows <= 0 or g.columns <= 0 then
+ g.rows = 0
+ g.columns = 0
+ g.effective_spacing[1] = g.size[1]
+ g.effective_spacing[2] = g.size[2]
+ return
+ end
+ if g.rows * g.columns > gallery.config.max_thumbnails then
+ local r = math.sqrt(g.rows * g.columns / gallery.config.max_thumbnails)
+ g.rows = math.floor(g.rows / r)
+ g.columns = math.floor(g.columns / r)
+ end
+ g.effective_spacing[1] = (g.size[1] - g.columns * g.thumbnail_size[1]) / (g.columns + 1)
+ g.effective_spacing[2] = (g.size[2] - g.rows * g.thumbnail_size[2]) / (g.rows + 1)
+end
+
+-- makes sure that view.first and view.last are valid with regards to the playlist
+-- and that selection is within the view
+-- to be called after the playlist, view or selection was modified somehow
+function gallery_mt.ensure_view_valid(gallery)
+ local g = gallery.geometry
+ if #gallery.items == 0 or g.rows == 0 or g.columns == 0 then
+ gallery.view.first = 0
+ gallery.view.last = 0
+ return
+ end
+ local v = gallery.view
+ local selection_row = math.floor((gallery.selection - 1) / g.columns)
+ local max_thumbs = g.rows * g.columns
+ local changed = false
+
+ if v.last >= #gallery.items then
+ v.last = #gallery.items
+ if g.rows == 1 then
+ v.first = math.max(1, v.last - g.columns + 1)
+ else
+ local last_row = math.floor((v.last - 1) / g.columns)
+ local first_row = math.max(0, last_row - g.rows + 1)
+ v.first = 1 + first_row * g.columns
+ end
+ changed = true
+ elseif v.first == 0 or v.last == 0 or v.last - v.first + 1 ~= max_thumbs then
+ -- special case: the number of possible thumbnails was changed
+ -- just recreate the view such that the selection is in the middle row
+ local max_row = (#gallery.items - 1) / g.columns + 1
+ local row_first = selection_row - math.floor((g.rows - 1) / 2)
+ local row_last = selection_row + math.floor((g.rows - 1) / 2) + g.rows % 2
+ if row_first < 0 then
+ row_first = 0
+ elseif row_last > max_row then
+ row_first = max_row - g.rows + 1
+ end
+ v.first = 1 + row_first * g.columns
+ v.last = math.min(#gallery.items, v.first - 1 + max_thumbs)
+ return true
+ end
+
+ if gallery.selection < v.first then
+ -- the selection is now on the first line
+ v.first = (g.rows == 1) and gallery.selection or selection_row * g.columns + 1
+ v.last = math.min(#gallery.items, v.first + max_thumbs - 1)
+ changed = true
+ elseif gallery.selection > v.last then
+ v.last = (g.rows == 1) and gallery.selection or (selection_row + 1) * g.columns
+ v.first = math.max(1, v.last - max_thumbs + 1)
+ v.last = math.min(#gallery.items, v.last)
+ changed = true
+ end
+ return changed
+end
+
+-- ass related stuff
+function gallery_mt.refresh_background(gallery)
+ local g = gallery.geometry
+ local a = assdraw.ass_new()
+ a:new_event()
+ a:append("{\\an7}")
+ a:append("{\\bord0}")
+ a:append("{\\shad0}")
+ a:append("{\\1c&" .. gallery.config.background_color .. "}")
+ a:append("{\\1a&" .. gallery.config.background_opacity .. "}")
+ a:pos(0, 0)
+ a:draw_start()
+ a:round_rect_cw(
+ g.position[1],
+ g.position[2],
+ g.position[1] + g.size[1],
+ g.position[2] + g.size[2],
+ gallery.config.background_roundness
+ )
+ a:draw_stop()
+ gallery.ass.background = a.text
+end
+
+function gallery_mt.refresh_placeholders(gallery)
+ if not gallery.config.show_placeholders then
+ return
+ end
+ if gallery.view.first == 0 then
+ gallery.ass.placeholders = ""
+ return
+ end
+ local g = gallery.geometry
+ local a = assdraw.ass_new()
+ a:new_event()
+ a:append("{\\an7}")
+ a:append("{\\bord0}")
+ a:append("{\\shad0}")
+ a:append("{\\1c&" .. gallery.config.placeholder_color .. "}")
+ a:pos(0, 0)
+ a:draw_start()
+ for i = 0, gallery.view.last - gallery.view.first do
+ if gallery.config.always_show_placeholders or not gallery.overlays.active[i + 1] then
+ local x, y = gallery:view_index_position(i)
+ a:rect_cw(x, y, x + g.thumbnail_size[1], y + g.thumbnail_size[2])
+ end
+ end
+ a:draw_stop()
+ gallery.ass.placeholders = a.text
+end
+
+function gallery_mt.refresh_scrollbar(gallery)
+ if not gallery.config.scrollbar then
+ return
+ end
+ gallery.ass.scrollbar = ""
+ if gallery.view.first == 0 then
+ return
+ end
+ local g = gallery.geometry
+ local before = (gallery.view.first - 1) / #gallery.items
+ local after = (#gallery.items - gallery.view.last) / #gallery.items
+ -- don't show the scrollbar if everything is visible
+ if before + after == 0 then
+ return
+ end
+ local p = gallery.config.scrollbar_min_size / 100
+ if before + after > 1 - p then
+ if before == 0 then
+ after = (1 - p)
+ elseif after == 0 then
+ before = (1 - p)
+ else
+ before, after =
+ before / after * (1 - p) / (1 + before / after), after / before * (1 - p) / (1 + after / before)
+ end
+ end
+ local dist_from_edge = g.size[2] * 0.015
+ local y1 = g.position[2] + dist_from_edge + before * (g.size[2] - 2 * dist_from_edge)
+ local y2 = g.position[2] + g.size[2] - (dist_from_edge + after * (g.size[2] - 2 * dist_from_edge))
+ local x1, x2
+ if gallery.config.scrollbar_left_side then
+ x1 = g.position[1] + g.effective_spacing[1] / 2 - 2
+ else
+ x1 = g.position[1] + g.size[1] - g.effective_spacing[1] / 2 - 2
+ end
+ x2 = x1 + 4
+ local scrollbar = assdraw.ass_new()
+ scrollbar:new_event()
+ scrollbar:append("{\\an7}")
+ scrollbar:append("{\\bord0}")
+ scrollbar:append("{\\shad0}")
+ scrollbar:append("{\\1c&AAAAAA&}")
+ scrollbar:pos(0, 0)
+ scrollbar:draw_start()
+ scrollbar:rect_cw(x1, y1, x2, y2)
+ scrollbar:draw_stop()
+ gallery.ass.scrollbar = scrollbar.text
+end
+
+function gallery_mt.refresh_selection(gallery)
+ local v = gallery.view
+ if v.first == 0 then
+ gallery.ass.selection = ""
+ return
+ end
+ local selection_ass = assdraw.ass_new()
+ local g = gallery.geometry
+ local draw_frame = function(index, size, color)
+ local x, y = gallery:view_index_position(index - v.first)
+ selection_ass:new_event()
+ selection_ass:append("{\\an7}")
+ selection_ass:append("{\\bord" .. size .. "}")
+ selection_ass:append("{\\3c&" .. color .. "&}")
+ selection_ass:append("{\\1a&FF&}")
+ selection_ass:pos(0, 0)
+ selection_ass:draw_start()
+ selection_ass:rect_cw(x, y, x + g.thumbnail_size[1], y + g.thumbnail_size[2])
+ selection_ass:draw_stop()
+ end
+ for i = v.first, v.last do
+ local size, color = gallery.item_to_border(i, gallery.items[i])
+ if size > 0 then
+ draw_frame(i, size, color)
+ end
+ end
+
+ for index = v.first, v.last do
+ local text = gallery.item_to_text(index, gallery.items[index])
+ if text ~= "" then
+ selection_ass:new_event()
+ local an = 5
+ local x, y = gallery:view_index_position(index - v.first)
+ x = x + g.thumbnail_size[1] / 2
+ y = y + g.thumbnail_size[2] + gallery.config.text_size * 0.75
+ if gallery.config.align_text then
+ local col = (index - v.first) % g.columns
+ if g.columns > 1 then
+ if col == 0 then
+ x = x - g.thumbnail_size[1] / 2
+ an = 4
+ elseif col == g.columns - 1 then
+ x = x + g.thumbnail_size[1] / 2
+ an = 6
+ end
+ end
+ end
+ selection_ass:an(an)
+ selection_ass:pos(x, y)
+ selection_ass:append(string.format("{\\fs%d}", gallery.config.text_size))
+ selection_ass:append("{\\bord0}")
+ selection_ass:append(text)
+ end
+ end
+ gallery.ass.selection = selection_ass.text
+end
+
+function gallery_mt.ass_refresh(gallery, selection, scrollbar, placeholders, background)
+ if not gallery.active then
+ return
+ end
+ if selection then
+ gallery:refresh_selection()
+ end
+ if scrollbar then
+ gallery:refresh_scrollbar()
+ end
+ if placeholders then
+ gallery:refresh_placeholders()
+ end
+ if background then
+ gallery:refresh_background()
+ end
+ gallery.ass_show(table.concat({
+ gallery.ass.background,
+ gallery.ass.placeholders,
+ gallery.ass.selection,
+ gallery.ass.scrollbar,
+ }, "\n"))
+end
+
+function gallery_mt.set_selection(gallery, selection)
+ if not selection or selection ~= selection then
+ return
+ end
+ local new_selection = math.max(1, math.min(selection, #gallery.items))
+ if gallery.selection == new_selection then
+ return
+ end
+ gallery.selection = new_selection
+ if gallery.active then
+ if gallery:ensure_view_valid() then
+ gallery:refresh_overlays(false)
+ gallery:ass_refresh(true, true, true, false)
+ else
+ gallery:ass_refresh(true, false, false, false)
+ end
+ end
+end
+
+function gallery_mt.set_geometry(gallery, x, y, w, h, sw, sh, tw, th)
+ if w <= 0 or h <= 0 or tw <= 0 or th <= 0 then
+ msg.warn("Invalid coordinates")
+ return
+ end
+ gallery.geometry.position = { x, y }
+ gallery.geometry.size = { w, h }
+ gallery.geometry.min_spacing = { sw, sh }
+ gallery.geometry.thumbnail_size = { tw, th }
+ gallery.geometry.ok = true
+ if not gallery.active then
+ return
+ end
+ if not gallery:enough_space() then
+ msg.warn("Not enough space to display something")
+ end
+ local old_total = gallery.geometry.rows * gallery.geometry.columns
+ gallery:compute_internal_geometry()
+ gallery:ensure_view_valid()
+ local new_total = gallery.geometry.rows * gallery.geometry.columns
+ for view_index = new_total + 1, old_total do
+ if gallery.overlays.active[view_index] then
+ mp.commandv("overlay-remove", gallery.config.overlay_range + view_index - 1)
+ gallery.overlays.active[view_index] = false
+ end
+ end
+ gallery:refresh_overlays(true)
+ gallery:ass_refresh(true, true, true, true)
+end
+
+function gallery_mt.items_changed(gallery, new_sel)
+ gallery.selection = math.max(1, math.min(new_sel, #gallery.items))
+ if not gallery.active then
+ return
+ end
+ gallery:ensure_view_valid()
+ gallery:refresh_overlays(false)
+ gallery:ass_refresh(true, true, true, false)
+end
+
+function gallery_mt.thumbnail_generated(gallery, thumb_path)
+ if not gallery.active then
+ return
+ end
+ local view_index = gallery.overlays.missing[thumb_path]
+ if view_index == nil then
+ return
+ end
+ gallery:show_overlay(view_index, thumb_path)
+ if not gallery.config.always_show_placeholders then
+ gallery:ass_refresh(false, false, true, false)
+ end
+ gallery.overlays.missing[thumb_path] = nil
+end
+
+function gallery_mt.add_generator(gallery, generator_name)
+ for _, g in ipairs(gallery.generators) do
+ if generator_name == g then
+ return
+ end
+ end
+ gallery.generators[#gallery.generators + 1] = generator_name
+end
+
+function gallery_mt.view_index_position(gallery, index_0)
+ local g = gallery.geometry
+ return math.floor(
+ g.position[1] + g.effective_spacing[1] + (g.effective_spacing[1] + g.thumbnail_size[1]) * (index_0 % g.columns)
+ ),
+ math.floor(
+ g.position[2]
+ + g.effective_spacing[2]
+ + (g.effective_spacing[2] + g.thumbnail_size[2]) * math.floor(index_0 / g.columns)
+ )
+end
+
+function gallery_mt.enough_space(gallery)
+ if gallery.geometry.size[1] < gallery.geometry.thumbnail_size[1] + 2 * gallery.geometry.min_spacing[1] then
+ return false
+ end
+ if gallery.geometry.size[2] < gallery.geometry.thumbnail_size[2] + 2 * gallery.geometry.min_spacing[2] then
+ return false
+ end
+ return true
+end
+
+function gallery_mt.activate(gallery)
+ if gallery.active then
+ return false
+ end
+ if not gallery:enough_space() then
+ msg.warn("Not enough space, refusing to start")
+ return false
+ end
+ if not gallery.geometry.ok then
+ msg.warn("Gallery geometry unitialized, refusing to start")
+ return false
+ end
+ gallery.active = true
+ if not gallery.selection then
+ gallery:set_selection(1)
+ end
+ gallery:compute_internal_geometry()
+ gallery:ensure_view_valid()
+ gallery:refresh_overlays(false)
+ gallery:ass_refresh(true, true, true, true)
+ return true
+end
+
+function gallery_mt.deactivate(gallery)
+ if not gallery.active then
+ return
+ end
+ gallery.active = false
+ gallery:remove_overlays()
+ gallery.ass_show("")
+end
+
+return { gallery_new = gallery_new }
diff --git a/ar/.config/mpv/script-modules/input-console.lua b/ar/.config/mpv/script-modules/input-console.lua
new file mode 100644
index 0000000..9128cba
--- /dev/null
+++ b/ar/.config/mpv/script-modules/input-console.lua
@@ -0,0 +1,935 @@
+-- copied from here: https://github.com/mpv-player/mpv/blob/ebaf6a6cfa24a78d9041389974568b9db7df6a71/player/lua/console.lua
+
+-- Copyright (C) 2019 the mpv developers
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+local utils = require("mp.utils")
+local options = require("mp.options")
+local assdraw = require("mp.assdraw")
+
+-- Default options
+local opts = {
+ -- All drawing is scaled by this value, including the text borders and the
+ -- cursor. Change it if you have a high-DPI display.
+ scale = 1.3,
+ -- Set the font used for the REPL and the console. This probably doesn't
+ -- have to be a monospaced font.
+ font = "",
+ -- Set the font size used for the REPL and the console. This will be
+ -- multiplied by "scale."
+ font_size = 16,
+}
+
+function detect_platform()
+ local o = {}
+ -- Kind of a dumb way of detecting the platform but whatever
+ if mp.get_property_native("options/vo-mmcss-profile", o) ~= o then
+ return "windows"
+ elseif mp.get_property_native("options/macos-force-dedicated-gpu", o) ~= o then
+ return "macos"
+ elseif os.getenv("WAYLAND_DISPLAY") then
+ return "wayland"
+ end
+ return "x11"
+end
+
+-- Pick a better default font for Windows and macOS
+local platform = detect_platform()
+if platform == "windows" then
+ opts.font = "Consolas"
+elseif platform == "macos" then
+ opts.font = "Menlo"
+else
+ opts.font = "monospace"
+end
+
+-- Apply user-set options
+options.read_options(opts)
+
+local repl_active = false
+local insert_mode = false
+local pending_update = false
+local line = ""
+local cursor = 1
+local history = {}
+local history_pos = 1
+local log_buffer = {}
+local key_bindings = {}
+local global_margin_y = 0
+
+local update_timer = nil
+update_timer = mp.add_periodic_timer(0.05, function()
+ if pending_update then
+ update()
+ else
+ update_timer:kill()
+ end
+end)
+update_timer:kill()
+
+if utils.shared_script_property_observe then
+ utils.shared_script_property_observe("osc-margins", function(_, val)
+ if val then
+ -- formatted as "%f,%f,%f,%f" with left, right, top, bottom, each
+ -- value being the border size as ratio of the window size (0.0-1.0)
+ local vals = {}
+ for v in string.gmatch(val, "[^,]+") do
+ vals[#vals + 1] = tonumber(v)
+ end
+ global_margin_y = vals[4] -- bottom
+ else
+ global_margin_y = 0
+ end
+ update()
+ end)
+else
+ mp.observe_property("user-data/osc/margins", "native", function(_, val)
+ if val then
+ global_margin_y = val.b
+ else
+ global_margin_y = 0
+ end
+ end)
+end
+
+-- Add a line to the log buffer (which is limited to 100 lines)
+function log_add(style, text)
+ log_buffer[#log_buffer + 1] = { style = style, text = text }
+ if #log_buffer > 100 then
+ table.remove(log_buffer, 1)
+ end
+
+ if repl_active then
+ if not update_timer:is_enabled() then
+ update()
+ update_timer:resume()
+ else
+ pending_update = true
+ end
+ end
+end
+
+-- Escape a string for verbatim display on the OSD
+function ass_escape(str)
+ -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
+ -- it isn't followed by a recognised character, so add a zero-width
+ -- non-breaking space
+ str = str:gsub("\\", "\\\239\187\191")
+ str = str:gsub("{", "\\{")
+ str = str:gsub("}", "\\}")
+ -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
+ -- consecutive newlines
+ str = str:gsub("\n", "\239\187\191\\N")
+ -- Turn leading spaces into hard spaces to prevent ASS from stripping them
+ str = str:gsub("\\N ", "\\N\\h")
+ str = str:gsub("^ ", "\\h")
+ return str
+end
+
+-- Render the REPL and console as an ASS OSD
+function update()
+ pending_update = false
+
+ local dpi_scale = mp.get_property_native("display-hidpi-scale", 1.0)
+
+ dpi_scale = dpi_scale * opts.scale
+
+ local screenx, screeny, aspect = mp.get_osd_size()
+ screenx = screenx / dpi_scale
+ screeny = screeny / dpi_scale
+
+ -- Clear the OSD if the REPL is not active
+ if not repl_active then
+ mp.set_osd_ass(screenx, screeny, "")
+ return
+ end
+
+ local ass = assdraw.ass_new()
+ local style = "{\\r"
+ .. "\\1a&H00&\\3a&H00&\\4a&H99&"
+ .. "\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&"
+ .. "\\fn"
+ .. opts.font
+ .. "\\fs"
+ .. opts.font_size
+ .. "\\bord1\\xshad0\\yshad1\\fsp0\\q1}"
+ -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor
+ -- inline with the surrounding text, but it sets the advance to the width
+ -- of the drawing. So the cursor doesn't affect layout too much, make it as
+ -- thin as possible and make it appear to be 1px wide by giving it 0.5px
+ -- horizontal borders.
+ local cheight = opts.font_size * 8
+ local cglyph = "{\\r"
+ .. "\\1a&H44&\\3a&H44&\\4a&H99&"
+ .. "\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&"
+ .. "\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}"
+ .. "m 0 0 l 1 0 l 1 "
+ .. cheight
+ .. " l 0 "
+ .. cheight
+ .. "{\\p0}"
+ local before_cur = ass_escape(line:sub(1, cursor - 1))
+ local after_cur = ass_escape(line:sub(cursor))
+
+ -- Render log messages as ASS. This will render at most screeny / font_size
+ -- messages.
+ local log_ass = ""
+ local log_messages = #log_buffer
+ local log_max_lines = math.ceil(screeny / opts.font_size)
+ if log_max_lines < log_messages then
+ log_messages = log_max_lines
+ end
+ for i = #log_buffer - log_messages + 1, #log_buffer do
+ log_ass = log_ass .. style .. log_buffer[i].style .. ass_escape(log_buffer[i].text)
+ end
+
+ ass:new_event()
+ ass:an(1)
+ ass:pos(2, screeny - 2 - global_margin_y * screeny)
+ ass:append(log_ass .. "\\N")
+ ass:append(style .. "> " .. before_cur)
+ ass:append(cglyph)
+ ass:append(style .. after_cur)
+
+ -- Redraw the cursor with the REPL text invisible. This will make the
+ -- cursor appear in front of the text.
+ ass:new_event()
+ ass:an(1)
+ ass:pos(2, screeny - 2 - global_margin_y * screeny)
+ ass:append(style .. "{\\alpha&HFF&}> " .. before_cur)
+ ass:append(cglyph)
+ ass:append(style .. "{\\alpha&HFF&}" .. after_cur)
+
+ mp.set_osd_ass(screenx, screeny, ass.text)
+end
+
+-- Set the REPL visibility ("enable", Esc)
+function set_active(active)
+ if active == repl_active then
+ return
+ end
+ if active then
+ repl_active = true
+ insert_mode = false
+ mp.enable_key_bindings("console-input", "allow-hide-cursor+allow-vo-dragging")
+ mp.enable_messages("terminal-default")
+ define_key_bindings()
+ else
+ repl_active = false
+ undefine_key_bindings()
+ mp.enable_messages("silent:terminal-default")
+ collectgarbage()
+ end
+ update()
+end
+
+-- Show the repl if hidden and replace its contents with 'text'
+-- (script-message-to repl type)
+function show_and_type(text, cursor_pos)
+ text = text or ""
+ cursor_pos = tonumber(cursor_pos)
+
+ -- Save the line currently being edited, just in case
+ if line ~= text and line ~= "" and history[#history] ~= line then
+ history[#history + 1] = line
+ end
+
+ line = text
+ if cursor_pos ~= nil and cursor_pos >= 1 and cursor_pos <= line:len() + 1 then
+ cursor = math.floor(cursor_pos)
+ else
+ cursor = line:len() + 1
+ end
+ history_pos = #history + 1
+ insert_mode = false
+ if repl_active then
+ update()
+ else
+ set_active(true)
+ end
+end
+
+-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
+-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
+function next_utf8(str, pos)
+ if pos > str:len() then
+ return pos
+ end
+ repeat
+ pos = pos + 1
+ until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
+
+-- As above, but finds the previous UTF-8 character in 'str' before 'pos'
+function prev_utf8(str, pos)
+ if pos <= 1 then
+ return pos
+ end
+ repeat
+ pos = pos - 1
+ until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
+
+-- Insert a character at the current cursor position (any_unicode)
+function handle_char_input(c)
+ if insert_mode then
+ line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
+ else
+ line = line:sub(1, cursor - 1) .. c .. line:sub(cursor)
+ end
+ cursor = cursor + #c
+ update()
+end
+
+-- Remove the character behind the cursor (Backspace)
+function handle_backspace()
+ if cursor <= 1 then
+ return
+ end
+ local prev = prev_utf8(line, cursor)
+ line = line:sub(1, prev - 1) .. line:sub(cursor)
+ cursor = prev
+ update()
+end
+
+-- Remove the character in front of the cursor (Del)
+function handle_del()
+ if cursor > line:len() then
+ return
+ end
+ line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
+ update()
+end
+
+-- Toggle insert mode (Ins)
+function handle_ins()
+ insert_mode = not insert_mode
+end
+
+-- Move the cursor to the next character (Right)
+function next_char(amount)
+ cursor = next_utf8(line, cursor)
+ update()
+end
+
+-- Move the cursor to the previous character (Left)
+function prev_char(amount)
+ cursor = prev_utf8(line, cursor)
+ update()
+end
+
+-- Clear the current line (Ctrl+C)
+function clear()
+ line = ""
+ cursor = 1
+ insert_mode = false
+ history_pos = #history + 1
+ update()
+end
+
+-- Close the REPL if the current line is empty, otherwise delete the next
+-- character (Ctrl+D)
+function maybe_exit()
+ if line == "" then
+ set_active(false)
+ else
+ handle_del()
+ end
+end
+
+function help_command(param)
+ local cmdlist = mp.get_property_native("command-list")
+ local error_style = "{\\1c&H7a77f2&}"
+ local output = ""
+ if param == "" then
+ output = "Available commands:\n"
+ for _, cmd in ipairs(cmdlist) do
+ output = output .. " " .. cmd.name
+ end
+ output = output .. "\n"
+ output = output .. 'Use "help command" to show information about a command.\n'
+ output = output .. "ESC or Ctrl+d exits the console.\n"
+ else
+ local cmd = nil
+ for _, curcmd in ipairs(cmdlist) do
+ if curcmd.name:find(param, 1, true) then
+ cmd = curcmd
+ if curcmd.name == param then
+ break -- exact match
+ end
+ end
+ end
+ if not cmd then
+ log_add(error_style, 'No command matches "' .. param .. '"!')
+ return
+ end
+ output = output .. 'Command "' .. cmd.name .. '"\n'
+ for _, arg in ipairs(cmd.args) do
+ output = output .. " " .. arg.name .. " (" .. arg.type .. ")"
+ if arg.optional then
+ output = output .. " (optional)"
+ end
+ output = output .. "\n"
+ end
+ if cmd.vararg then
+ output = output .. "This command supports variable arguments.\n"
+ end
+ end
+ log_add("", output)
+end
+
+local enter_handler = nil
+
+-- Run the current command and clear the line (Enter)
+function handle_enter()
+ if line == "" then
+ return
+ end
+
+ if history[#history] ~= line then
+ history[#history + 1] = line
+ end
+
+ if enter_handler then
+ enter_handler(line)
+ end
+
+ set_active(false)
+
+ clear()
+end
+
+-- Go to the specified position in the command history
+function go_history(new_pos)
+ local old_pos = history_pos
+ history_pos = new_pos
+
+ -- Restrict the position to a legal value
+ if history_pos > #history + 1 then
+ history_pos = #history + 1
+ elseif history_pos < 1 then
+ history_pos = 1
+ end
+
+ -- Do nothing if the history position didn't actually change
+ if history_pos == old_pos then
+ return
+ end
+
+ -- If the user was editing a non-history line, save it as the last history
+ -- entry. This makes it much less frustrating to accidentally hit Up/Down
+ -- while editing a line.
+ if old_pos == #history + 1 and line ~= "" and history[#history] ~= line then
+ history[#history + 1] = line
+ end
+
+ -- Now show the history line (or a blank line for #history + 1)
+ if history_pos <= #history then
+ line = history[history_pos]
+ else
+ line = ""
+ end
+ cursor = line:len() + 1
+ insert_mode = false
+ update()
+end
+
+-- Go to the specified relative position in the command history (Up, Down)
+function move_history(amount)
+ go_history(history_pos + amount)
+end
+
+-- Go to the first command in the command history (PgUp)
+function handle_pgup()
+ go_history(1)
+end
+
+-- Stop browsing history and start editing a blank line (PgDown)
+function handle_pgdown()
+ go_history(#history + 1)
+end
+
+-- Move to the start of the current word, or if already at the start, the start
+-- of the previous word. (Ctrl+Left)
+function prev_word()
+ -- This is basically the same as next_word() but backwards, so reverse the
+ -- string in order to do a "backwards" find. This wouldn't be as annoying
+ -- to do if Lua didn't insist on 1-based indexing.
+ cursor = line:len() - select(2, line:reverse():find("%s*[^%s]*", line:len() - cursor + 2)) + 1
+ update()
+end
+
+-- Move to the end of the current word, or if already at the end, the end of
+-- the next word. (Ctrl+Right)
+function next_word()
+ cursor = select(2, line:find("%s*[^%s]*", cursor)) + 1
+ update()
+end
+
+-- List of tab-completions:
+-- pattern: A Lua pattern used in string:find. Should return the start and
+-- end positions of the word to be completed in the first and second
+-- capture groups (using the empty parenthesis notation "()")
+-- list: A list of candidate completion values.
+-- append: An extra string to be appended to the end of a successful
+-- completion. It is only appended if 'list' contains exactly one
+-- match.
+function build_completers()
+ -- Build a list of commands, properties and options for tab-completion
+ local option_info = {
+ "name",
+ "type",
+ "set-from-commandline",
+ "set-locally",
+ "default-value",
+ "min",
+ "max",
+ "choices",
+ }
+ local cmd_list = {}
+ for i, cmd in ipairs(mp.get_property_native("command-list")) do
+ cmd_list[i] = cmd.name
+ end
+ local prop_list = mp.get_property_native("property-list")
+ for _, opt in ipairs(mp.get_property_native("options")) do
+ prop_list[#prop_list + 1] = "options/" .. opt
+ prop_list[#prop_list + 1] = "file-local-options/" .. opt
+ prop_list[#prop_list + 1] = "option-info/" .. opt
+ for _, p in ipairs(option_info) do
+ prop_list[#prop_list + 1] = "option-info/" .. opt .. "/" .. p
+ end
+ end
+
+ return {
+ { pattern = "^%s*()[%w_-]+()$", list = cmd_list, append = " " },
+ { pattern = "^%s*set%s+()[%w_/-]+()$", list = prop_list, append = " " },
+ { pattern = '^%s*set%s+"()[%w_/-]+()$', list = prop_list, append = '" ' },
+ { pattern = "^%s*add%s+()[%w_/-]+()$", list = prop_list, append = " " },
+ { pattern = '^%s*add%s+"()[%w_/-]+()$', list = prop_list, append = '" ' },
+ { pattern = "^%s*cycle%s+()[%w_/-]+()$", list = prop_list, append = " " },
+ { pattern = '^%s*cycle%s+"()[%w_/-]+()$', list = prop_list, append = '" ' },
+ { pattern = "^%s*multiply%s+()[%w_/-]+()$", list = prop_list, append = " " },
+ { pattern = '^%s*multiply%s+"()[%w_/-]+()$', list = prop_list, append = '" ' },
+ { pattern = "${()[%w_/-]+()$", list = prop_list, append = "}" },
+ }
+end
+
+-- Use 'list' to find possible tab-completions for 'part.' Returns the longest
+-- common prefix of all the matching list items and a flag that indicates
+-- whether the match was unique or not.
+function complete_match(part, list)
+ local completion = nil
+ local full_match = false
+
+ for _, candidate in ipairs(list) do
+ if candidate:sub(1, part:len()) == part then
+ if completion and completion ~= candidate then
+ local prefix_len = part:len()
+ while completion:sub(1, prefix_len + 1) == candidate:sub(1, prefix_len + 1) do
+ prefix_len = prefix_len + 1
+ end
+ completion = candidate:sub(1, prefix_len)
+ full_match = false
+ else
+ completion = candidate
+ full_match = true
+ end
+ end
+ end
+
+ return completion, full_match
+end
+
+-- Complete the option or property at the cursor (TAB)
+function complete()
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ -- Try the first completer that works
+ for _, completer in ipairs(build_completers()) do
+ -- Completer patterns should return the start and end of the word to be
+ -- completed as the first and second capture groups
+ local _, _, s, e = before_cur:find(completer.pattern)
+ if not s then
+ -- Multiple input commands can be separated by semicolons, so all
+ -- completions that are anchored at the start of the string with
+ -- '^' can start from a semicolon as well. Replace ^ with ; and try
+ -- to match again.
+ _, _, s, e = before_cur:find(completer.pattern:gsub("^^", ";"))
+ end
+ if s then
+ -- If the completer's pattern found a word, check the completer's
+ -- list for possible completions
+ local part = before_cur:sub(s, e)
+ local c, full = complete_match(part, completer.list)
+ if c then
+ -- If there was only one full match from the list, add
+ -- completer.append to the final string. This is normally a
+ -- space or a quotation mark followed by a space.
+ if full and completer.append then
+ c = c .. completer.append
+ end
+
+ -- Insert the completion and update
+ before_cur = before_cur:sub(1, s - 1) .. c
+ cursor = before_cur:len() + 1
+ line = before_cur .. after_cur
+ update()
+ return
+ end
+ end
+ end
+end
+
+-- Move the cursor to the beginning of the line (HOME)
+function go_home()
+ cursor = 1
+ update()
+end
+
+-- Move the cursor to the end of the line (END)
+function go_end()
+ cursor = line:len() + 1
+ update()
+end
+
+-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
+function del_word()
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ before_cur = before_cur:gsub("[^%s]+%s*$", "", 1)
+ line = before_cur .. after_cur
+ cursor = before_cur:len() + 1
+ update()
+end
+
+-- Delete from the cursor to the end of the word (Ctrl+Del)
+function del_next_word()
+ if cursor > line:len() then
+ return
+ end
+
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ after_cur = after_cur:gsub("^%s*[^%s]+", "", 1)
+ line = before_cur .. after_cur
+ update()
+end
+
+-- Delete from the cursor to the end of the line (Ctrl+K)
+function del_to_eol()
+ line = line:sub(1, cursor - 1)
+ update()
+end
+
+-- Delete from the cursor back to the start of the line (Ctrl+U)
+function del_to_start()
+ line = line:sub(cursor)
+ cursor = 1
+ update()
+end
+
+-- Empty the log buffer of all messages (Ctrl+L)
+function clear_log_buffer()
+ log_buffer = {}
+ update()
+end
+
+-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
+function get_clipboard(clip)
+ if platform == "x11" then
+ local res = utils.subprocess({
+ args = { "xclip", "-selection", clip and "clipboard" or "primary", "-out" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "wayland" then
+ local res = utils.subprocess({
+ args = { "wl-paste", clip and "-n" or "-np" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "windows" then
+ local res = utils.subprocess({
+ args = {
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ [[& {
+ Trap {
+ Write-Error -ErrorRecord $_
+ Exit 1
+ }
+
+ $clip = ""
+ if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
+ $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
+ } else {
+ Add-Type -AssemblyName PresentationCore
+ $clip = [Windows.Clipboard]::GetText()
+ }
+
+ $clip = $clip -Replace "`r",""
+ $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
+ [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
+ }]],
+ },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "macos" then
+ local res = utils.subprocess({
+ args = { "pbpaste" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ end
+ return ""
+end
+
+-- Paste text from the window-system's clipboard. 'clip' determines whether the
+-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
+function paste(clip)
+ local text = get_clipboard(clip)
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+ line = before_cur .. text .. after_cur
+ cursor = cursor + text:len()
+ update()
+end
+
+-- List of input bindings. This is a weird mashup between common GUI text-input
+-- bindings and readline bindings.
+function get_bindings()
+ local bindings = {
+ {
+ "esc",
+ function()
+ set_active(false)
+ end,
+ },
+ { "enter", handle_enter },
+ { "kp_enter", handle_enter },
+ {
+ "shift+enter",
+ function()
+ handle_char_input("\n")
+ end,
+ },
+ { "ctrl+j", handle_enter },
+ { "ctrl+m", handle_enter },
+ { "bs", handle_backspace },
+ { "shift+bs", handle_backspace },
+ { "ctrl+h", handle_backspace },
+ { "del", handle_del },
+ { "shift+del", handle_del },
+ { "ins", handle_ins },
+ {
+ "shift+ins",
+ function()
+ paste(false)
+ end,
+ },
+ {
+ "mbtn_mid",
+ function()
+ paste(false)
+ end,
+ },
+ {
+ "left",
+ function()
+ prev_char()
+ end,
+ },
+ {
+ "ctrl+b",
+ function()
+ prev_char()
+ end,
+ },
+ {
+ "right",
+ function()
+ next_char()
+ end,
+ },
+ {
+ "ctrl+f",
+ function()
+ next_char()
+ end,
+ },
+ {
+ "up",
+ function()
+ move_history(-1)
+ end,
+ },
+ {
+ "ctrl+p",
+ function()
+ move_history(-1)
+ end,
+ },
+ {
+ "wheel_up",
+ function()
+ move_history(-1)
+ end,
+ },
+ {
+ "down",
+ function()
+ move_history(1)
+ end,
+ },
+ {
+ "ctrl+n",
+ function()
+ move_history(1)
+ end,
+ },
+ {
+ "wheel_down",
+ function()
+ move_history(1)
+ end,
+ },
+ { "wheel_left", function() end },
+ { "wheel_right", function() end },
+ { "ctrl+left", prev_word },
+ { "alt+b", prev_word },
+ { "ctrl+right", next_word },
+ { "alt+f", next_word },
+ { "tab", complete },
+ { "ctrl+i", complete },
+ { "ctrl+a", go_home },
+ { "home", go_home },
+ { "ctrl+e", go_end },
+ { "end", go_end },
+ { "pgup", handle_pgup },
+ { "pgdwn", handle_pgdown },
+ { "ctrl+c", clear },
+ { "ctrl+d", maybe_exit },
+ { "ctrl+k", del_to_eol },
+ { "ctrl+l", clear_log_buffer },
+ { "ctrl+u", del_to_start },
+ {
+ "ctrl+v",
+ function()
+ paste(true)
+ end,
+ },
+ {
+ "meta+v",
+ function()
+ paste(true)
+ end,
+ },
+ { "ctrl+bs", del_word },
+ { "ctrl+w", del_word },
+ { "ctrl+del", del_next_word },
+ { "alt+d", del_next_word },
+ {
+ "kp_dec",
+ function()
+ handle_char_input(".")
+ end,
+ },
+ }
+
+ for i = 0, 9 do
+ bindings[#bindings + 1] = {
+ "kp" .. i,
+ function()
+ handle_char_input("" .. i)
+ end,
+ }
+ end
+
+ return bindings
+end
+
+local function text_input(info)
+ if info.key_text and (info.event == "press" or info.event == "down" or info.event == "repeat") then
+ handle_char_input(info.key_text)
+ end
+end
+
+function define_key_bindings()
+ if #key_bindings > 0 then
+ return
+ end
+ for _, bind in ipairs(get_bindings()) do
+ -- Generate arbitrary name for removing the bindings later.
+ local name = "_console_" .. (#key_bindings + 1)
+ key_bindings[#key_bindings + 1] = name
+ mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true })
+ end
+ mp.add_forced_key_binding("any_unicode", "_console_text", text_input, { repeatable = true, complex = true })
+ key_bindings[#key_bindings + 1] = "_console_text"
+end
+
+function undefine_key_bindings()
+ for _, name in ipairs(key_bindings) do
+ mp.remove_key_binding(name)
+ end
+ key_bindings = {}
+end
+
+-- Add a global binding for enabling the REPL. While it's enabled, its bindings
+-- will take over and it can be closed with ESC.
+mp.add_key_binding(nil, "enable", function()
+ set_active(true)
+end)
+
+-- Add a script-message to show the REPL and fill it with the provided text
+mp.register_script_message("type", function(text, cursor_pos)
+ show_and_type(text, cursor_pos)
+end)
+
+-- Redraw the REPL when the OSD size changes. This is needed because the
+-- PlayRes of the OSD will need to be adjusted.
+mp.observe_property("osd-width", "native", update)
+mp.observe_property("osd-height", "native", update)
+mp.observe_property("display-hidpi-scale", "native", update)
+
+-- Enable log messages. In silent mode, mpv will queue log messages in a buffer
+-- until enable_messages is called again without the silent: prefix.
+mp.enable_messages("silent:terminal-default")
+
+collectgarbage()
+
+return {
+ set_active = set_active,
+ is_repl_active = function()
+ return repl_active
+ end,
+ set_enter_handler = function(callback)
+ enter_handler = callback
+ end,
+}
diff --git a/ar/.config/mpv/script-modules/mpvSockets.lua b/ar/.config/mpv/script-modules/mpvSockets.lua
new file mode 100644
index 0000000..d745540
--- /dev/null
+++ b/ar/.config/mpv/script-modules/mpvSockets.lua
@@ -0,0 +1,36 @@
+-- mpvSockets, one socket per instance, removes socket on exit
+
+local utils = require("mp.utils")
+
+local function get_temp_path()
+ local directory_seperator = package.config:match("([^\n]*)\n?")
+ local example_temp_file_path = os.tmpname()
+
+ -- remove generated temp file
+ pcall(os.remove, example_temp_file_path)
+
+ local seperator_idx = example_temp_file_path:reverse():find(directory_seperator)
+ local temp_path_length = #example_temp_file_path - seperator_idx
+
+ return example_temp_file_path:sub(1, temp_path_length)
+end
+
+tempDir = get_temp_path()
+
+function join_paths(...)
+ local arg = { ... }
+ path = ""
+ for i, v in ipairs(arg) do
+ path = utils.join_path(path, tostring(v))
+ end
+ return path
+end
+
+ppid = utils.getpid()
+os.execute("mkdir " .. join_paths(tempDir, "mpvSockets") .. " 2>/dev/null")
+mp.set_property("options/input-ipc-server", join_paths(tempDir, "mpvSockets", ppid))
+
+function shutdown_handler()
+ os.remove(join_paths(tempDir, "mpvSockets", ppid))
+end
+mp.register_event("shutdown", shutdown_handler)
diff --git a/ar/.config/mpv/script-modules/scroll-list.lua b/ar/.config/mpv/script-modules/scroll-list.lua
new file mode 100644
index 0000000..5d8f9fa
--- /dev/null
+++ b/ar/.config/mpv/script-modules/scroll-list.lua
@@ -0,0 +1,293 @@
+local mp = require 'mp'
+local scroll_list = {
+ global_style = [[]],
+ header_style = [[{\q2\fs35\c&00ccff&}]],
+ list_style = [[{\q2\fs25\c&Hffffff&}]],
+ wrapper_style = [[{\c&00ccff&\fs16}]],
+ cursor_style = [[{\c&00ccff&}]],
+ selected_style = [[{\c&Hfce788&}]],
+
+ cursor = [[➤\h]],
+ indent = [[\h\h\h\h]],
+
+ num_entries = 16,
+ wrap = false,
+ empty_text = "no entries"
+}
+
+--formats strings for ass handling
+--this function is based on a similar function from https://github.com/mpv-player/mpv/blob/master/player/lua/console.lua#L110
+function scroll_list.ass_escape(str, replace_newline)
+ if replace_newline == true then replace_newline = "\\\239\187\191n" end
+
+ --escape the invalid single characters
+ str = str:gsub('[\\{}\n]', {
+ -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
+ -- it isn't followed by a recognised character, so add a zero-width
+ -- non-breaking space
+ ['\\'] = '\\\239\187\191',
+ ['{'] = '\\{',
+ ['}'] = '\\}',
+ -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
+ -- consecutive newlines
+ ['\n'] = '\239\187\191\\N',
+ })
+
+ -- Turn leading spaces into hard spaces to prevent ASS from stripping them
+ str = str:gsub('\\N ', '\\N\\h')
+ str = str:gsub('^ ', '\\h')
+
+ if replace_newline then
+ str = str:gsub("\\N", replace_newline)
+ end
+ return str
+end
+
+--format and return the header string
+function scroll_list:format_header_string(str)
+ return str
+end
+
+--appends the entered text to the overlay
+function scroll_list:append(text)
+ if text == nil then return end
+ self.ass.data = self.ass.data .. text
+ end
+
+--appends a newline character to the osd
+function scroll_list:newline()
+ self.ass.data = self.ass.data .. '\\N'
+end
+
+--re-parses the list into an ass string
+--if the list is closed then it flags an update on the next open
+function scroll_list:update()
+ if self.hidden then self.flag_update = true
+ else self:update_ass() end
+end
+
+--prints the header to the overlay
+function scroll_list:format_header()
+ self:append(self.header_style)
+ self:append(self:format_header_string(self.header))
+ self:newline()
+end
+
+--formats each line of the list and prints it to the overlay
+function scroll_list:format_line(index, item)
+ self:append(self.list_style)
+
+ if index == self.selected then self:append(self.cursor_style..self.cursor..self.selected_style)
+ else self:append(self.indent) end
+
+ self:append(item.style)
+ self:append(item.ass)
+ self:newline()
+end
+
+--refreshes the ass text using the contents of the list
+function scroll_list:update_ass()
+ self.ass.data = self.global_style
+ self:format_header()
+
+ if #self.list < 1 then
+ self:append(self.empty_text)
+ self.ass:update()
+ return
+ end
+
+ local start = 1
+ local finish = start+self.num_entries-1
+
+ --handling cursor positioning
+ local mid = math.ceil(self.num_entries/2)+1
+ if self.selected+mid > finish then
+ local offset = self.selected - finish + mid
+
+ --if we've overshot the end of the list then undo some of the offset
+ if finish + offset > #self.list then
+ offset = offset - ((finish+offset) - #self.list)
+ end
+
+ start = start + offset
+ finish = finish + offset
+ end
+
+ --making sure that we don't overstep the boundaries
+ if start < 1 then start = 1 end
+ local overflow = finish < #self.list
+ --this is necessary when the number of items in the dir is less than the max
+ if not overflow then finish = #self.list end
+
+ --adding a header to show there are items above in the list
+ if start > 1 then self:append(self.wrapper_style..(start-1)..' item(s) above\\N\\N') end
+
+ for i=start, finish do
+ self:format_line(i, self.list[i])
+ end
+
+ if overflow then self:append('\\N'..self.wrapper_style..#self.list-finish..' item(s) remaining') end
+ self.ass:update()
+end
+
+--moves the selector down the list
+function scroll_list:scroll_down()
+ if self.selected < #self.list then
+ self.selected = self.selected + 1
+ self:update_ass()
+ elseif self.wrap then
+ self.selected = 1
+ self:update_ass()
+ end
+end
+
+--moves the selector up the list
+function scroll_list:scroll_up()
+ if self.selected > 1 then
+ self.selected = self.selected - 1
+ self:update_ass()
+ elseif self.wrap then
+ self.selected = #self.list
+ self:update_ass()
+ end
+end
+
+--moves the selector to the list next page
+function scroll_list:move_pagedown()
+ if #self.list > self.num_entries then
+ self.selected = self.selected + self.num_entries
+ if self.selected > #self.list then self.selected = #self.list end
+ self:update_ass()
+ end
+end
+
+--moves the selector to the list previous page
+function scroll_list:move_pageup()
+ if #self.list > self.num_entries then
+ self.selected = self.selected - self.num_entries
+ if self.selected < 1 then self.selected = 1 end
+ self:update_ass()
+ end
+end
+
+--moves the selector to the list begin
+function scroll_list:move_begin()
+ if #self.list > 1 then
+ self.selected = 1
+ self:update_ass()
+ end
+end
+
+--moves the selector to the list end
+function scroll_list:move_end()
+ if #self.list > 1 then
+ self.selected = #self.list
+ self:update_ass()
+ end
+end
+
+--adds the forced keybinds
+function scroll_list:add_keybinds()
+ for _,v in ipairs(self.keybinds) do
+ mp.add_forced_key_binding(v[1], 'dynamic/'..self.ass.id..'/'..v[2], v[3], v[4])
+ end
+end
+
+--removes the forced keybinds
+function scroll_list:remove_keybinds()
+ for _,v in ipairs(self.keybinds) do
+ mp.remove_key_binding('dynamic/'..self.ass.id..'/'..v[2])
+ end
+end
+
+--opens the list and sets the hidden flag
+function scroll_list:open_list()
+ self.hidden = false
+ if not self.flag_update then self.ass:update()
+ else self.flag_update = false ; self:update_ass() end
+end
+
+--closes the list and sets the hidden flag
+function scroll_list:close_list()
+ self.hidden = true
+ self.ass:remove()
+end
+
+--modifiable function that opens the list
+function scroll_list:open()
+ if self.hidden then self:add_keybinds() end
+ self:open_list()
+end
+
+--modifiable function that closes the list
+function scroll_list:close()
+ self:remove_keybinds()
+ self:close_list()
+end
+
+--toggles the list
+function scroll_list:toggle()
+ if self.hidden then self:open()
+ else self:close() end
+end
+
+--clears the list in-place
+function scroll_list:clear()
+ local i = 1
+ while self.list[i] do
+ self.list[i] = nil
+ i = i + 1
+ end
+end
+
+--added alias for ipairs(list.list) for lua 5.1
+function scroll_list:ipairs()
+ return ipairs(self.list)
+end
+
+--append item to the end of the list
+function scroll_list:insert(item)
+ self.list[#self.list + 1] = item
+end
+
+local metatable = {
+ __index = function(t, key)
+ if scroll_list[key] ~= nil then return scroll_list[key]
+ elseif key == "__current" then return t.list[t.selected]
+ elseif type(key) == "number" then return t.list[key] end
+ end,
+ __newindex = function(t, key, value)
+ if type(key) == "number" then rawset(t.list, key, value)
+ else rawset(t, key, value) end
+ end,
+ __scroll_list = scroll_list,
+ __len = function(t) return #t.list end,
+ __ipairs = function(t) return ipairs(t.list) end
+}
+
+--creates a new list object
+function scroll_list:new()
+ local vars
+ vars = {
+ ass = mp.create_osd_overlay('ass-events'),
+ hidden = true,
+ flag_update = true,
+
+ header = "header \\N ----------------------------------------------",
+ list = {},
+ selected = 1,
+
+ keybinds = {
+ {'DOWN', 'scroll_down', function() vars:scroll_down() end, {repeatable = true}},
+ {'UP', 'scroll_up', function() vars:scroll_up() end, {repeatable = true}},
+ {'PGDWN', 'move_pagedown', function() vars:move_pagedown() end, {}},
+ {'PGUP', 'move_pageup', function() vars:move_pageup() end, {}},
+ {'HOME', 'move_begin', function() vars:move_begin() end, {}},
+ {'END', 'move_end', function() vars:move_end() end, {}},
+ {'ESC', 'close_browser', function() vars:close() end, {}}
+ }
+ }
+ return setmetatable(vars, metatable)
+end
+
+return scroll_list:new()
diff --git a/ar/.config/mpv/script-modules/sha1.lua b/ar/.config/mpv/script-modules/sha1.lua
new file mode 100644
index 0000000..6b19396
--- /dev/null
+++ b/ar/.config/mpv/script-modules/sha1.lua
@@ -0,0 +1,334 @@
+-- $Revision: 1.5 $
+-- $Date: 2014-09-10 16:54:25 $
+
+-- This module was originally taken from http://cube3d.de/uploads/Main/sha1.txt.
+
+-------------------------------------------------------------------------------
+-- SHA-1 secure hash computation, and HMAC-SHA1 signature computation,
+-- in pure Lua (tested on Lua 5.1)
+-- License: MIT
+--
+-- Usage:
+-- local hashAsHex = sha1.hex(message) -- returns a hex string
+-- local hashAsData = sha1.bin(message) -- returns raw bytes
+--
+-- local hmacAsHex = sha1.hmacHex(key, message) -- hex string
+-- local hmacAsData = sha1.hmacBin(key, message) -- raw bytes
+--
+--
+-- Pass sha1.hex() a string, and it returns a hash as a 40-character hex string.
+-- For example, the call
+--
+-- local hash = sha1.hex("iNTERFACEWARE")
+--
+-- puts the 40-character string
+--
+-- "e76705ffb88a291a0d2f9710a5471936791b4819"
+--
+-- into the variable 'hash'
+--
+-- Pass sha1.hmacHex() a key and a message, and it returns the signature as a
+-- 40-byte hex string.
+--
+--
+-- The two "bin" versions do the same, but return the 20-byte string of raw
+-- data that the 40-byte hex strings represent.
+--
+-------------------------------------------------------------------------------
+--
+-- Description
+-- Due to the lack of bitwise operations in 5.1, this version uses numbers to
+-- represents the 32bit words that we combine with binary operations. The basic
+-- operations of byte based "xor", "or", "and" are all cached in a combination
+-- table (several 64k large tables are built on startup, which
+-- consumes some memory and time). The caching can be switched off through
+-- setting the local cfg_caching variable to false.
+-- For all binary operations, the 32 bit numbers are split into 8 bit values
+-- that are combined and then merged again.
+--
+-- Algorithm: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+--
+-------------------------------------------------------------------------------
+
+sha1 = {}
+
+-- set this to false if you don't want to build several 64k sized tables when
+-- loading this file (takes a while but grants a boost of factor 13)
+local cfg_caching = false
+
+-- local storing of global functions (minor speedup)
+local floor, modf = math.floor, math.modf
+local char, format, rep = string.char, string.format, string.rep
+
+-- merge 4 bytes to an 32 bit word
+local function bytes_to_w32(a, b, c, d) return a * 0x1000000 + b * 0x10000 + c * 0x100 + d end
+
+-- split a 32 bit word into four 8 bit numbers
+local function w32_to_bytes(i)
+ return floor(i / 0x1000000) % 0x100, floor(i / 0x10000) % 0x100, floor(i / 0x100) % 0x100, i % 0x100
+end
+
+-- shift the bits of a 32 bit word. Don't use negative values for "bits"
+local function w32_rot(bits, a)
+ local b2 = 2 ^ (32 - bits)
+ local a, b = modf(a / b2)
+ return a + b * b2 * (2 ^ (bits))
+end
+
+-- caching function for functions that accept 2 arguments, both of values between
+-- 0 and 255. The function to be cached is passed, all values are calculated
+-- during loading and a function is returned that returns the cached values (only)
+local function cache2arg(fn)
+ if not cfg_caching then return fn end
+ local lut = {}
+ for i = 0, 0xffff do
+ local a, b = floor(i / 0x100), i % 0x100
+ lut[i] = fn(a, b)
+ end
+ return function(a, b)
+ return lut[a * 0x100 + b]
+ end
+end
+
+-- splits an 8-bit number into 8 bits, returning all 8 bits as booleans
+local function byte_to_bits(b)
+ local b = function(n)
+ local b = floor(b / n)
+ return b % 2 == 1
+ end
+ return b(1), b(2), b(4), b(8), b(16), b(32), b(64), b(128)
+end
+
+-- builds an 8bit number from 8 booleans
+local function bits_to_byte(a, b, c, d, e, f, g, h)
+ local function n(b, x) return b and x or 0 end
+
+ return n(a, 1) + n(b, 2) + n(c, 4) + n(d, 8) + n(e, 16) + n(f, 32) + n(g, 64) + n(h, 128)
+end
+
+-- debug function for visualizing bits in a string
+local function bits_to_string(a, b, c, d, e, f, g, h)
+ local function x(b) return b and "1" or "0" end
+
+ return ("%s%s%s%s %s%s%s%s"):format(x(a), x(b), x(c), x(d), x(e), x(f), x(g), x(h))
+end
+
+-- debug function for converting a 8-bit number as bit string
+local function byte_to_bit_string(b)
+ return bits_to_string(byte_to_bits(b))
+end
+
+-- debug function for converting a 32 bit number as bit string
+local function w32_to_bit_string(a)
+ if type(a) == "string" then return a end
+ local aa, ab, ac, ad = w32_to_bytes(a)
+ local s = byte_to_bit_string
+ return ("%s %s %s %s"):format(s(aa):reverse(), s(ab):reverse(), s(ac):reverse(), s(ad):reverse()):reverse()
+end
+
+-- bitwise "and" function for 2 8bit number
+local band = cache2arg(function(a, b)
+ local A, B, C, D, E, F, G, H = byte_to_bits(b)
+ local a, b, c, d, e, f, g, h = byte_to_bits(a)
+ return bits_to_byte(
+ A and a, B and b, C and c, D and d,
+ E and e, F and f, G and g, H and h)
+end)
+
+-- bitwise "or" function for 2 8bit numbers
+local bor = cache2arg(function(a, b)
+ local A, B, C, D, E, F, G, H = byte_to_bits(b)
+ local a, b, c, d, e, f, g, h = byte_to_bits(a)
+ return bits_to_byte(
+ A or a, B or b, C or c, D or d,
+ E or e, F or f, G or g, H or h)
+end)
+
+-- bitwise "xor" function for 2 8bit numbers
+local bxor = cache2arg(function(a, b)
+ local A, B, C, D, E, F, G, H = byte_to_bits(b)
+ local a, b, c, d, e, f, g, h = byte_to_bits(a)
+ return bits_to_byte(
+ A ~= a, B ~= b, C ~= c, D ~= d,
+ E ~= e, F ~= f, G ~= g, H ~= h)
+end)
+
+-- bitwise complement for one 8bit number
+local function bnot(x)
+ return 255 - (x % 256)
+end
+
+-- creates a function to combine to 32bit numbers using an 8bit combination function
+local function w32_comb(fn)
+ return function(a, b)
+ local aa, ab, ac, ad = w32_to_bytes(a)
+ local ba, bb, bc, bd = w32_to_bytes(b)
+ return bytes_to_w32(fn(aa, ba), fn(ab, bb), fn(ac, bc), fn(ad, bd))
+ end
+end
+
+-- create functions for and, xor and or, all for 2 32bit numbers
+local w32_and = w32_comb(band)
+local w32_xor = w32_comb(bxor)
+local w32_or = w32_comb(bor)
+
+-- xor function that may receive a variable number of arguments
+local function w32_xor_n(a, ...)
+ local aa, ab, ac, ad = w32_to_bytes(a)
+ for i = 1, select('#', ...) do
+ local ba, bb, bc, bd = w32_to_bytes(select(i, ...))
+ aa, ab, ac, ad = bxor(aa, ba), bxor(ab, bb), bxor(ac, bc), bxor(ad, bd)
+ end
+ return bytes_to_w32(aa, ab, ac, ad)
+end
+
+-- combining 3 32bit numbers through binary "or" operation
+local function w32_or3(a, b, c)
+ local aa, ab, ac, ad = w32_to_bytes(a)
+ local ba, bb, bc, bd = w32_to_bytes(b)
+ local ca, cb, cc, cd = w32_to_bytes(c)
+ return bytes_to_w32(
+ bor(aa, bor(ba, ca)), bor(ab, bor(bb, cb)), bor(ac, bor(bc, cc)), bor(ad, bor(bd, cd))
+ )
+end
+
+-- binary complement for 32bit numbers
+local function w32_not(a)
+ return 4294967295 - (a % 4294967296)
+end
+
+-- adding 2 32bit numbers, cutting off the remainder on 33th bit
+local function w32_add(a, b) return (a + b) % 4294967296 end
+
+-- adding n 32bit numbers, cutting off the remainder (again)
+local function w32_add_n(a, ...)
+ for i = 1, select('#', ...) do
+ a = (a + select(i, ...)) % 4294967296
+ end
+ return a
+end
+
+-- converting the number to a hexadecimal string
+local function w32_to_hexstring(w) return format("%08x", w) end
+
+-- calculating the SHA1 for some text
+function sha1.hex(msg)
+ local H0, H1, H2, H3, H4 = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
+ local msg_len_in_bits = #msg * 8
+
+ local first_append = char(0x80) -- append a '1' bit plus seven '0' bits
+
+ local non_zero_message_bytes = #msg + 1 + 8 -- the +1 is the appended bit 1, the +8 are for the final appended length
+ local current_mod = non_zero_message_bytes % 64
+ local second_append = current_mod > 0 and rep(char(0), 64 - current_mod) or ""
+
+ -- now to append the length as a 64-bit number.
+ local B1, R1 = modf(msg_len_in_bits / 0x01000000)
+ local B2, R2 = modf(0x01000000 * R1 / 0x00010000)
+ local B3, R3 = modf(0x00010000 * R2 / 0x00000100)
+ local B4 = 0x00000100 * R3
+
+ local L64 = char(0) .. char(0) .. char(0) .. char(0) -- high 32 bits
+ .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits
+
+ msg = msg .. first_append .. second_append .. L64
+
+ assert(#msg % 64 == 0)
+
+ local chunks = #msg / 64
+
+ local W = {}
+ local start, A, B, C, D, E, f, K, TEMP
+ local chunk = 0
+
+ while chunk < chunks do
+ --
+ -- break chunk up into W[0] through W[15]
+ --
+ start, chunk = chunk * 64 + 1, chunk + 1
+
+ for t = 0, 15 do
+ W[t] = bytes_to_w32(msg:byte(start, start + 3))
+ start = start + 4
+ end
+
+ --
+ -- build W[16] through W[79]
+ --
+ for t = 16, 79 do
+ -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
+ W[t] = w32_rot(1, w32_xor_n(W[t - 3], W[t - 8], W[t - 14], W[t - 16]))
+ end
+
+ A, B, C, D, E = H0, H1, H2, H3, H4
+
+ for t = 0, 79 do
+ if t <= 19 then
+ -- (B AND C) OR ((NOT B) AND D)
+ f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
+ K = 0x5A827999
+ elseif t <= 39 then
+ -- B XOR C XOR D
+ f = w32_xor_n(B, C, D)
+ K = 0x6ED9EBA1
+ elseif t <= 59 then
+ -- (B AND C) OR (B AND D) OR (C AND D
+ f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
+ K = 0x8F1BBCDC
+ else
+ -- B XOR C XOR D
+ f = w32_xor_n(B, C, D)
+ K = 0xCA62C1D6
+ end
+
+ -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
+ A, B, C, D, E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
+ A, w32_rot(30, B), C, D
+ end
+ -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
+ H0, H1, H2, H3, H4 = w32_add(H0, A), w32_add(H1, B), w32_add(H2, C), w32_add(H3, D), w32_add(H4, E)
+ end
+ local f = w32_to_hexstring
+ return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
+end
+
+local function hex_to_binary(hex)
+ return hex:gsub('..', function(hexval)
+ return string.char(tonumber(hexval, 16))
+ end)
+end
+
+function sha1.bin(msg)
+ return hex_to_binary(sha1.hex(msg))
+end
+
+local xor_with_0x5c = {}
+local xor_with_0x36 = {}
+-- building the lookuptables ahead of time (instead of littering the source code
+-- with precalculated values)
+for i = 0, 0xff do
+ xor_with_0x5c[char(i)] = char(bxor(i, 0x5c))
+ xor_with_0x36[char(i)] = char(bxor(i, 0x36))
+end
+
+local blocksize = 64 -- 512 bits
+
+function sha1.hmacHex(key, text)
+ assert(type(key) == 'string', "key passed to hmacHex should be a string")
+ assert(type(text) == 'string', "text passed to hmacHex should be a string")
+
+ if #key > blocksize then
+ key = sha1.bin(key)
+ end
+
+ local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key)
+ local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key)
+
+ return sha1.hex(key_xord_with_0x5c .. sha1.bin(key_xord_with_0x36 .. text))
+end
+
+function sha1.hmacBin(key, text)
+ return hex_to_binary(sha1.hmacHex(key, text))
+end
+
+return sha1
diff --git a/ar/.config/mpv/script-modules/user-input-module.lua b/ar/.config/mpv/script-modules/user-input-module.lua
new file mode 100644
index 0000000..f15d5c4
--- /dev/null
+++ b/ar/.config/mpv/script-modules/user-input-module.lua
@@ -0,0 +1,126 @@
+--[[
+ This is a module designed to interface with mpv-user-input
+ https://github.com/CogentRedTester/mpv-user-input
+
+ Loading this script as a module will return a table with two functions to format
+ requests to get and cancel user-input requests. See the README for details.
+
+ Alternatively, developers can just paste these functions directly into their script,
+ however this is not recommended as there is no guarantee that the formatting of
+ these requests will remain the same for future versions of user-input.
+]]
+
+local API_VERSION = "0.1.0"
+
+local mp = require 'mp'
+local msg = require "mp.msg"
+local utils = require 'mp.utils'
+local mod = {}
+
+local name = mp.get_script_name()
+local counter = 1
+
+local function pack(...)
+ local t = {...}
+ t.n = select("#", ...)
+ return t
+end
+
+local request_mt = {}
+
+-- ensures the option tables are correctly formatted based on the input
+local function format_options(options, response_string)
+ return {
+ response = response_string,
+ version = API_VERSION,
+ id = name..'/'..(options.id or ""),
+ source = name,
+ request_text = ("[%s] %s"):format(options.source or name, options.request_text or options.text or "requesting user input:"),
+ default_input = options.default_input,
+ cursor_pos = tonumber(options.cursor_pos),
+ queueable = options.queueable and true,
+ replace = options.replace and true
+ }
+end
+
+-- cancels the request
+function request_mt:cancel()
+ assert(self.uid, "request object missing UID")
+ mp.commandv("script-message-to", "user_input", "cancel-user-input/uid", self.uid)
+end
+
+-- updates the options for the request
+function request_mt:update(options)
+ assert(self.uid, "request object missing UID")
+ options = utils.format_json( format_options(options) )
+ mp.commandv("script-message-to", "user_input", "update-user-input/uid", self.uid, options)
+end
+
+-- sends a request to ask the user for input using formatted options provided
+-- creates a script message to recieve the response and call fn
+function mod.get_user_input(fn, options, ...)
+ options = options or {}
+ local response_string = name.."/__user_input_request/"..counter
+ counter = counter + 1
+
+ local request = {
+ uid = response_string,
+ passthrough_args = pack(...),
+ callback = fn,
+ pending = true
+ }
+
+ -- create a callback for user-input to respond to
+ mp.register_script_message(response_string, function(response)
+ mp.unregister_script_message(response_string)
+ request.pending = false
+
+ response = utils.parse_json(response)
+ request.callback(response.line, response.err, unpack(request.passthrough_args, 1, request.passthrough_args.n))
+ end)
+
+ -- send the input command
+ options = utils.format_json( format_options(options, response_string) )
+ mp.commandv("script-message-to", "user_input", "request-user-input", options)
+
+ return setmetatable(request, { __index = request_mt })
+end
+
+-- runs the request synchronously using coroutines
+-- takes the option table and an optional coroutine resume function
+function mod.get_user_input_co(options, co_resume)
+ local co, main = coroutine.running()
+ assert(not main and co, "get_user_input_co must be run from within a coroutine")
+
+ local uid = {}
+ local request = mod.get_user_input(function(line, err)
+ if co_resume then
+ co_resume(uid, line, err)
+ else
+ local success, er = coroutine.resume(co, uid, line, err)
+ if not success then
+ msg.warn(debug.traceback(co))
+ msg.error(er)
+ end
+ end
+ end, options)
+
+ -- if the uid was not sent then the coroutine was resumed by the user.
+ -- we will treat this as a cancellation request
+ local success, line, err = coroutine.yield(request)
+ if success ~= uid then
+ request:cancel()
+ request.callback = function() end
+ return nil, "cancelled"
+ end
+
+ return line, err
+end
+
+-- sends a request to cancel all input requests with the given id
+function mod.cancel_user_input(id)
+ id = name .. '/' .. (id or "")
+ mp.commandv("script-message-to", "user_input", "cancel-user-input/id", id)
+end
+
+return mod \ No newline at end of file
diff --git a/ar/.config/mpv/script-modules/utf8/LICENSE b/ar/.config/mpv/script-modules/utf8/LICENSE
new file mode 100644
index 0000000..fd3b301
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Stepets
+
+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.
diff --git a/ar/.config/mpv/script-modules/utf8/README.md b/ar/.config/mpv/script-modules/utf8/README.md
new file mode 100644
index 0000000..0c31574
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/README.md
@@ -0,0 +1,93 @@
+# utf8.lua
+pure-lua 5.3 regex library for Lua 5.3, Lua 5.1, LuaJIT
+
+This library provides simple way to add UTF-8 support into your application.
+
+#### Example:
+```Lua
+local utf8 = require('.utf8'):init()
+for k,v in pairs(utf8) do
+ string[k] = v
+end
+
+local str = "пыщпыщ ололоо я водитель нло"
+print(str:find("(.л.+)н"))
+-- 8 26 ололоо я водитель
+
+print(str:gsub("ло+", "보라"))
+-- пыщпыщ о보라보라 я водитель н보라 3
+
+print(str:match("^п[лопыщ ]*я"))
+-- пыщпыщ ололоо я
+```
+
+#### Usage:
+
+This library can be used as drop-in replacement for vanilla string library. It exports all vanilla functions under `raw` sub-object.
+
+```Lua
+local utf8 = require('.utf8'):init()
+local str = "пыщпыщ ололоо я водитель нло"
+utf8.gsub(str, "ло+", "보라")
+-- пыщпыщ о보라보라 я водитель н보라 3
+utf8.raw.gsub(str, "ло+", "보라")
+-- пыщпыщ о보라보라о я водитель н보라 3
+```
+
+It also provides all functions from Lua 5.3 UTF-8 [module](https://www.lua.org/manual/5.3/manual.html#6.5) except `utf8.len (s [, i [, j]])`. If you need to validate your strings use `utf8.validate(str, byte_pos)` or iterate over with `utf8.validator`.
+
+Please note that library assumes regexes are valid UTF-8 strings, if you need to manipulate individual bytes use vanilla functions under `utf8.raw`.
+
+
+#### Installation:
+
+Download repository to your project folder. (no rockspecs yet)
+
+Examples assume library placed under `utf8` subfolder not `utf8.lua`.
+
+As of Lua 5.3 default `utf8` module has precedence over user-provided. In this case you can specify full module path (`.utf8`).
+
+#### Configuration:
+
+Library is highly modular. You can provide your implementation for almost any function used. Library already has several back-ends:
+- [Runtime character class processing](charclass/runtime/init.lua) using hardcoded codepoint ranges or using native functions through `ffi`.
+- [Basic functions](primitives/init.lua) for working with UTF-8 characters have specializations for `ffi`-enabled runtime and for tarantool.
+
+Probably most interesting [customizations](init.lua) are `utf8.config.loadstring` and `utf8.config.cache` if you want to precompile your regexes.
+
+```Lua
+local utf8 = require('.utf8')
+utf8.config = {
+ cache = my_smart_cache,
+}
+utf8:init()
+```
+
+For `lower` and `upper` functions to work in environments where `ffi` cannot be used, you can specify substitution tables ([data example](https://github.com/artemshein/luv/blob/master/utf8data.lua))
+
+```Lua
+local utf8 = require('.utf8')
+utf8.config = {
+ conversion = {
+ uc_lc = utf8_uc_lc,
+ lc_uc = utf8_lc_uc
+ },
+}
+utf8:init()
+```
+Customization is done before initialization. If you want, you can change configuration after `init`, it might work for everything but modules. All of them should be reloaded.
+
+#### [Documentation:](test/test.lua)
+
+#### Issue reporting:
+
+Please provide example script that causes error together with environment description and debug output. Debug output can be obtained like:
+```Lua
+local utf8 = require('.utf8')
+utf8.config = {
+ debug = utf8:require("util").debug
+}
+utf8:init()
+-- your code
+```
+Default logger used is [`io.write`](https://www.lua.org/manual/5.3/manual.html#pdf-io.write) and can be changed by specifying `logger = my_logger` in configuration
diff --git a/ar/.config/mpv/script-modules/utf8/begins/compiletime/parser.lua b/ar/.config/mpv/script-modules/utf8/begins/compiletime/parser.lua
new file mode 100644
index 0000000..c54c0df
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/begins/compiletime/parser.lua
@@ -0,0 +1,17 @@
+return function(utf8)
+
+utf8.config.begins = utf8.config.begins or {
+ utf8:require "begins.compiletime.vanilla"
+}
+
+function utf8.regex.compiletime.begins.parse(regex, c, bs, ctx)
+ for _, m in ipairs(utf8.config.begins) do
+ local functions, move = m.parse(regex, c, bs, ctx)
+ utf8.debug("begins", _, c, bs, move, functions)
+ if functions then
+ return functions, move
+ end
+ end
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/begins/compiletime/vanilla.lua b/ar/.config/mpv/script-modules/utf8/begins/compiletime/vanilla.lua
new file mode 100644
index 0000000..bcafa17
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/begins/compiletime/vanilla.lua
@@ -0,0 +1,60 @@
+return function(utf8)
+
+local matchers = {
+ sliding = function()
+ return [[
+ add(function(ctx) -- sliding
+ while ctx.pos <= ctx.len do
+ local clone = ctx:clone()
+ -- debug('starting from', clone, "start_pos", clone.pos)
+ clone.result.start = clone.pos
+ clone:next_function()
+ clone:get_function()(clone)
+
+ ctx:next_char()
+ end
+ ctx:terminate()
+ end)
+]]
+ end,
+ fromstart = function(ctx)
+ return [[
+ add(function(ctx) -- fromstart
+ if ctx.byte_pos > ctx.len then
+ return
+ end
+ ctx.result.start = ctx.pos
+ ctx:next_function()
+ ctx:get_function()(ctx)
+ ctx:terminate()
+ end)
+]]
+ end,
+}
+
+local function default()
+ return matchers.sliding()
+end
+
+local function parse(regex, c, bs, ctx)
+ if bs ~= 1 then return end
+
+ local functions
+ local skip = 0
+
+ if c == '^' then
+ functions = matchers.fromstart()
+ skip = 1
+ else
+ functions = matchers.sliding()
+ end
+
+ return functions, skip
+end
+
+return {
+ parse = parse,
+ default = default,
+}
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/compiletime/builder.lua b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/builder.lua
new file mode 100644
index 0000000..9d9c603
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/builder.lua
@@ -0,0 +1,128 @@
+return function(utf8)
+
+local byte = utf8.byte
+local unpack = utf8.config.unpack
+
+local builder = {}
+local mt = {__index = builder}
+
+utf8.regex.compiletime.charclass.builder = builder
+
+function builder.new()
+ return setmetatable({}, mt)
+end
+
+function builder:invert()
+ self.inverted = true
+ return self
+end
+
+function builder:internal() -- is it enclosed in []
+ self.internal = true
+ return self
+end
+
+function builder:with_codes(...)
+ local codes = {...}
+ self.codes = self.codes or {}
+
+ for _, v in ipairs(codes) do
+ table.insert(self.codes, type(v) == "number" and v or byte(v))
+ end
+
+ table.sort(self.codes)
+ return self
+end
+
+function builder:with_ranges(...)
+ local ranges = {...}
+ self.ranges = self.ranges or {}
+
+ for _, v in ipairs(ranges) do
+ table.insert(self.ranges, v)
+ end
+
+ return self
+end
+
+function builder:with_classes(...)
+ local classes = {...}
+ self.classes = self.classes or {}
+
+ for _, v in ipairs(classes) do
+ table.insert(self.classes, v)
+ end
+
+ return self
+end
+
+function builder:without_classes(...)
+ local not_classes = {...}
+ self.not_classes = self.not_classes or {}
+
+ for _, v in ipairs(not_classes) do
+ table.insert(self.not_classes, v)
+ end
+
+ return self
+end
+
+function builder:include(b)
+ if not b.inverted then
+ if b.codes then
+ self:with_codes(unpack(b.codes))
+ end
+ if b.ranges then
+ self:with_ranges(unpack(b.ranges))
+ end
+ if b.classes then
+ self:with_classes(unpack(b.classes))
+ end
+ if b.not_classes then
+ self:without_classes(unpack(b.not_classes))
+ end
+ else
+ self.includes = self.includes or {}
+ self.includes[#self.includes + 1] = b
+ end
+ return self
+end
+
+function builder:build()
+ if self.codes and #self.codes == 1 and not self.inverted and not self.ranges and not self.classes and not self.not_classes and not self.includes then
+ return "{test = function(self, cc) return cc == " .. self.codes[1] .. " end}"
+ else
+ local codes_list = table.concat(self.codes or {}, ', ')
+ local ranges_list = ''
+ for i, r in ipairs(self.ranges or {}) do ranges_list = ranges_list .. (i > 1 and ', {' or '{') .. tostring(r[1]) .. ', ' .. tostring(r[2]) .. '}' end
+ local classes_list = ''
+ if self.classes then classes_list = "'" .. table.concat(self.classes, "', '") .. "'" end
+ local not_classes_list = ''
+ if self.not_classes then not_classes_list = "'" .. table.concat(self.not_classes, "', '") .. "'" end
+
+ local subs_list = ''
+ for i, r in ipairs(self.includes or {}) do subs_list = subs_list .. (i > 1 and ', ' or '') .. r:build() .. '' end
+
+ local src = [[cl.new():with_codes(
+ ]] .. codes_list .. [[
+ ):with_ranges(
+ ]] .. ranges_list .. [[
+ ):with_classes(
+ ]] .. classes_list .. [[
+ ):without_classes(
+ ]] .. not_classes_list .. [[
+ ):with_subs(
+ ]] .. subs_list .. [[
+ )]]
+
+ if self.inverted then
+ src = src .. ':invert()'
+ end
+
+ return src
+ end
+end
+
+return builder
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/compiletime/parser.lua b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/parser.lua
new file mode 100644
index 0000000..4f1d4a9
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/parser.lua
@@ -0,0 +1,21 @@
+return function(utf8)
+
+utf8.config.compiletime_charclasses = utf8.config.compiletime_charclasses or {
+ utf8:require "charclass.compiletime.vanilla",
+ utf8:require "charclass.compiletime.range",
+ utf8:require "charclass.compiletime.stub",
+}
+
+function utf8.regex.compiletime.charclass.parse(regex, c, bs, ctx)
+ utf8.debug("parse charclass():", regex, c, bs, regex[bs])
+ for _, p in ipairs(utf8.config.compiletime_charclasses) do
+ local charclass, nbs = p(regex, c, bs, ctx)
+ if charclass then
+ ctx.prev_class = charclass:build()
+ utf8.debug("cc", ctx.prev_class, _, c, bs, nbs)
+ return charclass, nbs
+ end
+ end
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/compiletime/range.lua b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/range.lua
new file mode 100644
index 0000000..2996234
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/range.lua
@@ -0,0 +1,44 @@
+return function(utf8)
+
+local cl = utf8.regex.compiletime.charclass.builder
+
+local next = utf8.util.next
+
+return function(str, c, bs, ctx)
+ if not ctx.internal then return end
+
+ local nbs = bs
+
+ local r1, r2
+
+ local c, nbs = c, bs
+ if c == '%' then
+ c, nbs = next(str, nbs)
+ r1 = c
+ else
+ r1 = c
+ end
+
+ utf8.debug("range r1", r1, nbs)
+
+ c, nbs = next(str, nbs)
+ if c ~= '-' then return end
+
+ c, nbs = next(str, nbs)
+ if c == '%' then
+ c, nbs = next(str, nbs)
+ r2 = c
+ elseif c ~= '' and c ~= ']' then
+ r2 = c
+ end
+
+ utf8.debug("range r2", r2, nbs)
+
+ if r1 and r2 then
+ return cl.new():with_ranges{utf8.byte(r1), utf8.byte(r2)}, utf8.next(str, nbs) - bs
+ else
+ return
+ end
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/compiletime/stub.lua b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/stub.lua
new file mode 100644
index 0000000..395d05c
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/stub.lua
@@ -0,0 +1,9 @@
+return function(utf8)
+
+local cl = utf8.regex.compiletime.charclass.builder
+
+return function(str, c, bs, ctx)
+ return cl.new():with_codes(c), utf8.next(str, bs) - bs
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/compiletime/vanilla.lua b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/vanilla.lua
new file mode 100644
index 0000000..8e7f0b3
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/compiletime/vanilla.lua
@@ -0,0 +1,131 @@
+return function(utf8)
+
+local cl = utf8:require "charclass.compiletime.builder"
+
+local next = utf8.util.next
+
+local token = 1
+
+local function parse(str, c, bs, ctx)
+ local tttt = token
+ token = token + 1
+
+ local class
+ local nbs = bs
+ utf8.debug("cc_parse", tttt, str, c, nbs, next(str, nbs))
+
+ if c == '%' then
+ c, nbs = next(str, bs)
+ if c == '' then
+ error("malformed pattern (ends with '%')")
+ end
+ local _c = utf8.raw.lower(c)
+ local matched
+ if _c == 'a' then
+ matched = ('alpha')
+ elseif _c == 'c' then
+ matched = ('cntrl')
+ elseif _c == 'd' then
+ matched = ('digit')
+ elseif _c == 'g' then
+ matched = ('graph')
+ elseif _c == 'l' then
+ matched = ('lower')
+ elseif _c == 'p' then
+ matched = ('punct')
+ elseif _c == 's' then
+ matched = ('space')
+ elseif _c == 'u' then
+ matched = ('upper')
+ elseif _c == 'w' then
+ matched = ('alnum')
+ elseif _c == 'x' then
+ matched = ('xdigit')
+ end
+
+ if matched then
+ if _c ~= c then
+ class = cl.new():without_classes(matched)
+ else
+ class = cl.new():with_classes(matched)
+ end
+ elseif _c == 'z' then
+ class = cl.new():with_codes(0)
+ if _c ~= c then
+ class = class:invert()
+ end
+ else
+ class = cl.new():with_codes(c)
+ end
+ elseif c == '[' and not ctx.internal then
+ local old_internal = ctx.internal
+ ctx.internal = true
+ class = cl.new()
+ local firstletter = true
+ while true do
+ local prev_nbs = nbs
+ c, nbs = next(str, nbs)
+ utf8.debug("next", tttt, c, nbs)
+ if c == '^' and firstletter then
+ class:invert()
+ local nc, nnbs = next(str, nbs)
+ if nc == ']' then
+ class:with_codes(nc)
+ nbs = nnbs
+ end
+ elseif c == ']' then
+ if firstletter then
+ class:with_codes(c)
+ else
+ utf8.debug('] on pos', tttt, nbs)
+ break
+ end
+ elseif c == '' then
+ error "malformed pattern (missing ']')"
+ else
+ local sub_class, skip = utf8.regex.compiletime.charclass.parse(str, c, nbs, ctx)
+ nbs = prev_nbs + skip
+ utf8.debug("include", tttt, bs, prev_nbs, nbs, skip)
+ class:include(sub_class)
+ end
+ firstletter = false
+ end
+ ctx.internal = old_internal
+ elseif c == '.' then
+ if not ctx.internal then
+ class = cl.new():invert()
+ else
+ class = cl.new():with_codes(c)
+ end
+ end
+
+ return class, utf8.next(str, nbs) - bs
+end
+
+return parse
+
+end
+
+--[[
+ x: (where x is not one of the magic characters ^$()%.[]*+-?) represents the character x itself.
+ .: (a dot) represents all characters.
+ %a: represents all letters.
+ %c: represents all control characters.
+ %d: represents all digits.
+ %g: represents all printable characters except space.
+ %l: represents all lowercase letters.
+ %p: represents all punctuation characters.
+ %s: represents all space characters.
+ %u: represents all uppercase letters.
+ %w: represents all alphanumeric characters.
+ %x: represents all hexadecimal digits.
+ %x: (where x is any non-alphanumeric character) represents the character x. This is the standard way to escape the magic characters. Any non-alphanumeric character (including all punctuation characters, even the non-magical) can be preceded by a '%' when used to represent itself in a pattern.
+ [set]: represents the class which is the union of all characters in set. A range of characters can be specified by separating the end characters of the range, in ascending order, with a '-'. All classes %x described above can also be used as components in set. All other characters in set represent themselves. For example, [%w_] (or [_%w]) represents all alphanumeric characters plus the underscore, [0-7] represents the octal digits, and [0-7%l%-] represents the octal digits plus the lowercase letters plus the '-' character.
+
+ You can put a closing square bracket in a set by positioning it as the first character in the set. You can put a hyphen in a set by positioning it as the first or the last character in the set. (You can also use an escape for both cases.)
+
+ The interaction between ranges and classes is not defined. Therefore, patterns like [%a-z] or [a-%%] have no meaning.
+ [^set]: represents the complement of set, where set is interpreted as above.
+
+For all classes represented by single letters (%a, %c, etc.), the corresponding uppercase letter represents the complement of the class. For instance, %S represents all non-space characters.
+]]
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/runtime/base.lua b/ar/.config/mpv/script-modules/utf8/charclass/runtime/base.lua
new file mode 100644
index 0000000..33d7713
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/runtime/base.lua
@@ -0,0 +1,184 @@
+return function(utf8)
+
+local class = {}
+local mt = {__index = class}
+
+local utf8gensub = utf8.gensub
+
+function class.new()
+ return setmetatable({}, mt)
+end
+
+function class:invert()
+ self.inverted = true
+ return self
+end
+
+function class:with_codes(...)
+ local codes = {...}
+ self.codes = self.codes or {}
+
+ for _, v in ipairs(codes) do
+ table.insert(self.codes, v)
+ end
+
+ table.sort(self.codes)
+ return self
+end
+
+function class:with_ranges(...)
+ local ranges = {...}
+ self.ranges = self.ranges or {}
+
+ for _, v in ipairs(ranges) do
+ table.insert(self.ranges, v)
+ end
+
+ return self
+end
+
+function class:with_classes(...)
+ local classes = {...}
+ self.classes = self.classes or {}
+
+ for _, v in ipairs(classes) do
+ table.insert(self.classes, v)
+ end
+
+ return self
+end
+
+function class:without_classes(...)
+ local not_classes = {...}
+ self.not_classes = self.not_classes or {}
+
+ for _, v in ipairs(not_classes) do
+ table.insert(self.not_classes, v)
+ end
+
+ return self
+end
+
+function class:with_subs(...)
+ local subs = {...}
+ self.subs = self.subs or {}
+
+ for _, v in ipairs(subs) do
+ table.insert(self.subs, v)
+ end
+
+ return self
+end
+
+function class:in_codes(item)
+ if not self.codes or #self.codes == 0 then return nil end
+
+ local head, tail = 1, #self.codes
+ local mid = math.floor((head + tail)/2)
+ while (tail - head) > 1 do
+ if self.codes[mid] > item then
+ tail = mid
+ else
+ head = mid
+ end
+ mid = math.floor((head + tail)/2)
+ end
+ if self.codes[head] == item then
+ return true, head
+ elseif self.codes[tail] == item then
+ return true, tail
+ else
+ return false
+ end
+end
+
+function class:in_ranges(char_code)
+ if not self.ranges or #self.ranges == 0 then return nil end
+
+ for _,r in ipairs(self.ranges) do
+ if r[1] <= char_code and char_code <= r[2] then
+ return true
+ end
+ end
+ return false
+end
+
+function class:in_classes(char_code)
+ if not self.classes or #self.classes == 0 then return nil end
+
+ for _, class in ipairs(self.classes) do
+ if self:is(class, char_code) then
+ return true
+ end
+ end
+ return false
+end
+
+function class:in_not_classes(char_code)
+ if not self.not_classes or #self.not_classes == 0 then return nil end
+
+ for _, class in ipairs(self.not_classes) do
+ if self:is(class, char_code) then
+ return true
+ end
+ end
+ return false
+end
+
+function class:is(class, char_code)
+ error("not implemented")
+end
+
+function class:in_subs(char_code)
+ if not self.subs or #self.subs == 0 then return nil end
+
+ for _, c in ipairs(self.subs) do
+ if not c:test(char_code) then
+ return false
+ end
+ end
+ return true
+end
+
+function class:test(char_code)
+ local result = self:do_test(char_code)
+ -- utf8.debug('class:test', result, "'" .. (char_code and utf8.char(char_code) or 'nil') .. "'", char_code)
+ return result
+end
+
+function class:do_test(char_code)
+ if not char_code then return false end
+ local in_not_classes = self:in_not_classes(char_code)
+ if in_not_classes then
+ return not not self.inverted
+ end
+ local in_codes = self:in_codes(char_code)
+ if in_codes then
+ return not self.inverted
+ end
+ local in_ranges = self:in_ranges(char_code)
+ if in_ranges then
+ return not self.inverted
+ end
+ local in_classes = self:in_classes(char_code)
+ if in_classes then
+ return not self.inverted
+ end
+ local in_subs = self:in_subs(char_code)
+ if in_subs then
+ return not self.inverted
+ end
+ if (in_codes == nil)
+ and (in_ranges == nil)
+ and (in_classes == nil)
+ and (in_subs == nil)
+ and (in_not_classes == false) then
+ return not self.inverted
+ else
+ return not not self.inverted
+ end
+end
+
+return class
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/runtime/dummy.lua b/ar/.config/mpv/script-modules/utf8/charclass/runtime/dummy.lua
new file mode 100644
index 0000000..1faddc1
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/runtime/dummy.lua
@@ -0,0 +1,41 @@
+return function(utf8)
+
+local base = utf8:require "charclass.runtime.base"
+
+local dummy = setmetatable({}, {__index = base})
+local mt = {__index = dummy}
+
+function dummy.new()
+ return setmetatable({}, mt)
+end
+
+function dummy:with_classes(...)
+ local classes = {...}
+ for _, c in ipairs(classes) do
+ if c == 'alpha' then self:with_ranges({65, 90}, {97, 122})
+ elseif c == 'cntrl' then self:with_ranges({0, 31}):with_codes(127)
+ elseif c == 'digit' then self:with_ranges({48, 57})
+ elseif c == 'graph' then self:with_ranges({1, 8}, {14, 31}, {33, 132}, {134, 159}, {161, 5759}, {5761, 8191}, {8203, 8231}, {8234, 8238}, {8240, 8286}, {8288, 12287})
+ elseif c == 'lower' then self:with_ranges({97, 122})
+ elseif c == 'punct' then self:with_ranges({33, 47}, {58, 64}, {91, 96}, {123, 126})
+ elseif c == 'space' then self:with_ranges({9, 13}):with_codes(32, 133, 160, 5760):with_ranges({8192, 8202}):with_codes(8232, 8233, 8239, 8287, 12288)
+ elseif c == 'upper' then self:with_ranges({65, 90})
+ elseif c == 'alnum' then self:with_ranges({48, 57}, {65, 90}, {97, 122})
+ elseif c == 'xdigit' then self:with_ranges({48, 57}, {65, 70}, {97, 102})
+ end
+ end
+ return self
+end
+
+function dummy:without_classes(...)
+ local classes = {...}
+ if #classes > 0 then
+ return self:with_subs(dummy.new():with_classes(...):invert())
+ else
+ return self
+ end
+end
+
+return dummy
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/runtime/init.lua b/ar/.config/mpv/script-modules/utf8/charclass/runtime/init.lua
new file mode 100644
index 0000000..e71d037
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/runtime/init.lua
@@ -0,0 +1,22 @@
+return function(utf8)
+
+local provided = utf8.config.runtime_charclasses
+
+if provided then
+ if type(provided) == "table" then
+ return provided
+ elseif type(provided) == "function" then
+ return provided(utf8)
+ else
+ return utf8:require(provided)
+ end
+end
+
+local ffi = pcall(require, "ffi")
+if not ffi then
+ return utf8:require "charclass.runtime.dummy"
+else
+ return utf8:require "charclass.runtime.native"
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/charclass/runtime/native.lua b/ar/.config/mpv/script-modules/utf8/charclass/runtime/native.lua
new file mode 100644
index 0000000..f7b7890
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/charclass/runtime/native.lua
@@ -0,0 +1,47 @@
+return function(utf8)
+
+os.setlocale(utf8.config.locale, "ctype")
+
+local ffi = require("ffi")
+ffi.cdef[[
+ int iswalnum(int c);
+ int iswalpha(int c);
+ int iswascii(int c);
+ int iswblank(int c);
+ int iswcntrl(int c);
+ int iswdigit(int c);
+ int iswgraph(int c);
+ int iswlower(int c);
+ int iswprint(int c);
+ int iswpunct(int c);
+ int iswspace(int c);
+ int iswupper(int c);
+ int iswxdigit(int c);
+]]
+
+local base = utf8:require "charclass.runtime.base"
+
+local native = setmetatable({}, {__index = base})
+local mt = {__index = native}
+
+function native.new()
+ return setmetatable({}, mt)
+end
+
+function native:is(class, char_code)
+ if class == 'alpha' then return ffi.C.iswalpha(char_code) ~= 0
+ elseif class == 'cntrl' then return ffi.C.iswcntrl(char_code) ~= 0
+ elseif class == 'digit' then return ffi.C.iswdigit(char_code) ~= 0
+ elseif class == 'graph' then return ffi.C.iswgraph(char_code) ~= 0
+ elseif class == 'lower' then return ffi.C.iswlower(char_code) ~= 0
+ elseif class == 'punct' then return ffi.C.iswpunct(char_code) ~= 0
+ elseif class == 'space' then return ffi.C.iswspace(char_code) ~= 0
+ elseif class == 'upper' then return ffi.C.iswupper(char_code) ~= 0
+ elseif class == 'alnum' then return ffi.C.iswalnum(char_code) ~= 0
+ elseif class == 'xdigit' then return ffi.C.iswxdigit(char_code) ~= 0
+ end
+end
+
+return native
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/context/compiletime.lua b/ar/.config/mpv/script-modules/utf8/context/compiletime.lua
new file mode 100644
index 0000000..621204d
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/context/compiletime.lua
@@ -0,0 +1,18 @@
+return function(utf8)
+
+local begins = utf8.config.begins
+local ends = utf8.config.ends
+
+return {
+ new = function()
+ return {
+ prev_class = nil,
+ begins = begins[1].default(),
+ ends = ends[1].default(),
+ funcs = {},
+ internal = false, -- hack for ranges, flags if parser is in []
+ }
+ end
+}
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/context/runtime.lua b/ar/.config/mpv/script-modules/utf8/context/runtime.lua
new file mode 100644
index 0000000..6fb024c
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/context/runtime.lua
@@ -0,0 +1,112 @@
+return function(utf8)
+
+local utf8unicode = utf8.unicode
+local utf8sub = utf8.sub
+local sub = utf8.raw.sub
+local byte = utf8.raw.byte
+local utf8len = utf8.len
+local utf8next = utf8.next
+local rawgsub = utf8.raw.gsub
+local utf8offset = utf8.offset
+local utf8char = utf8.char
+
+local util = utf8.util
+
+local ctx = {}
+local mt = {
+ __index = ctx,
+ __tostring = function(self)
+ return rawgsub([[str: '${str}', char: ${pos} '${char}', func: ${func_pos}]], "${(.-)}", {
+ str = self.str,
+ pos = self.pos,
+ char = self:get_char(),
+ func_pos = self.func_pos,
+ })
+ end
+}
+
+function ctx.new(obj)
+ obj = obj or {}
+ local res = setmetatable({
+ pos = obj.pos or 1,
+ byte_pos = obj.pos or 1,
+ str = assert(obj.str, "str is required"),
+ len = obj.len,
+ rawlen = obj.rawlen,
+ bytes = obj.bytes,
+ offsets = obj.offsets,
+ starts = obj.starts or nil,
+ functions = obj.functions or {},
+ func_pos = obj.func_pos or 1,
+ ends = obj.ends or nil,
+ result = obj.result and util.copy(obj.result) or {},
+ captures = obj.captures and util.copy(obj.captures, true) or {active = {}},
+ modified = false,
+ }, mt)
+ if not res.bytes then
+ local str = res.str
+ local l = #str
+ local bytes = utf8.config.int32array(l)
+ local offsets = utf8.config.int32array(l)
+ local c, bs, i = nil, 1, 1
+ while bs <= l do
+ bytes[i] = utf8unicode(str, bs, bs)
+ offsets[i] = bs
+ bs = utf8.next(str, bs)
+ i = i + 1
+ end
+ res.bytes = bytes
+ res.offsets = offsets
+ res.byte_pos = res.pos
+ res.len = i
+ res.rawlen = l
+ end
+
+ return res
+end
+
+function ctx:clone()
+ return self:new()
+end
+
+function ctx:next_char()
+ self.pos = self.pos + 1
+ self.byte_pos = self.pos
+end
+
+function ctx:prev_char()
+ self.pos = self.pos - 1
+ self.byte_pos = self.pos
+end
+
+function ctx:get_char()
+ if self.len <= self.pos then return "" end
+ return utf8char(self.bytes[self.pos])
+end
+
+function ctx:get_charcode()
+ if self.len <= self.pos then return nil end
+ return self.bytes[self.pos]
+end
+
+function ctx:next_function()
+ self.func_pos = self.func_pos + 1
+end
+
+function ctx:get_function()
+ return self.functions[self.func_pos]
+end
+
+function ctx:done()
+ utf8.debug('done', self)
+ coroutine.yield(self, self.result, self.captures)
+end
+
+function ctx:terminate()
+ utf8.debug('terminate', self)
+ coroutine.yield(nil)
+end
+
+return ctx
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/ends/compiletime/parser.lua b/ar/.config/mpv/script-modules/utf8/ends/compiletime/parser.lua
new file mode 100644
index 0000000..f966e94
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/ends/compiletime/parser.lua
@@ -0,0 +1,17 @@
+return function(utf8)
+
+utf8.config.ends = utf8.config.ends or {
+ utf8:require "ends.compiletime.vanilla"
+}
+
+function utf8.regex.compiletime.ends.parse(regex, c, bs, ctx)
+ for _, m in ipairs(utf8.config.ends) do
+ local functions, move = m.parse(regex, c, bs, ctx)
+ utf8.debug("ends", _, c, bs, move, functions)
+ if functions then
+ return functions, move
+ end
+ end
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/ends/compiletime/vanilla.lua b/ar/.config/mpv/script-modules/utf8/ends/compiletime/vanilla.lua
new file mode 100644
index 0000000..5fe7eb3
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/ends/compiletime/vanilla.lua
@@ -0,0 +1,46 @@
+return function(utf8)
+
+local matchers = {
+ any = function()
+ return [[
+ add(function(ctx) -- any
+ ctx.result.finish = ctx.pos - 1
+ ctx:done()
+ end)
+]]
+ end,
+ toend = function(ctx)
+ return [[
+ add(function(ctx) -- toend
+ ctx.result.finish = ctx.pos - 1
+ ctx.modified = true
+ if ctx.pos == utf8len(ctx.str) + 1 then ctx:done() end
+ end)
+]]
+ end,
+}
+
+local len = utf8.raw.len
+
+local function default()
+ return matchers.any()
+end
+
+local function parse(regex, c, bs, ctx)
+ local functions
+ local skip = 0
+
+ if bs == len(regex) and c == '$' then
+ functions = matchers.toend()
+ skip = 1
+ end
+
+ return functions, skip
+end
+
+return {
+ parse = parse,
+ default = default,
+}
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/functions/lua53.lua b/ar/.config/mpv/script-modules/utf8/functions/lua53.lua
new file mode 100644
index 0000000..26e6f23
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/functions/lua53.lua
@@ -0,0 +1,152 @@
+return function(utf8)
+
+local utf8sub = utf8.sub
+local utf8gensub = utf8.gensub
+local unpack = utf8.config.unpack
+local generate_matcher_function = utf8:require 'regex_parser'
+
+local
+function get_matcher_function(regex, plain)
+ local res
+ if utf8.config.cache then
+ res = utf8.config.cache[plain and "plain" or "regex"][regex]
+ end
+ if res then
+ return res
+ end
+ res = generate_matcher_function(regex, plain)
+ if utf8.config.cache then
+ utf8.config.cache[plain and "plain" or "regex"][regex] = res
+ end
+ return res
+end
+
+local function utf8find(str, regex, init, plain)
+ local func = get_matcher_function(regex, plain)
+ init = ((init or 1) < 0) and (utf8.len(str) + init + 1) or init
+ local ctx, result, captures = func(str, init, utf8)
+ if not ctx then return nil end
+
+ utf8.debug('ctx:', ctx)
+ utf8.debug('result:', result)
+ utf8.debug('captures:', captures)
+
+ return result.start, result.finish, unpack(captures)
+end
+
+local function utf8match(str, regex, init)
+ local func = get_matcher_function(regex, false)
+ init = ((init or 1) < 0) and (utf8.len(str) + init + 1) or init
+ local ctx, result, captures = func(str, init, utf8)
+ if not ctx then return nil end
+
+ utf8.debug('ctx:', ctx)
+ utf8.debug('result:', result)
+ utf8.debug('captures:', captures)
+
+ if #captures > 0 then return unpack(captures) end
+
+ return utf8sub(str, result.start, result.finish)
+end
+
+local function utf8gmatch(str, regex)
+ regex = (utf8sub(regex,1,1) ~= '^') and regex or '%' .. regex
+ local func = get_matcher_function(regex, false)
+ local ctx, result, captures
+ local continue_pos = 1
+
+ return function()
+ ctx, result, captures = func(str, continue_pos, utf8)
+
+ if not ctx then return nil end
+
+ utf8.debug('ctx:', ctx)
+ utf8.debug('result:', result)
+ utf8.debug('captures:', captures)
+
+ continue_pos = math.max(result.finish + 1, result.start + 1)
+ if #captures > 0 then
+ return unpack(captures)
+ else
+ return utf8sub(str, result.start, result.finish)
+ end
+ end
+end
+
+local function replace(repl, args)
+ local ret = ''
+ if type(repl) == 'string' then
+ local ignore = false
+ local num
+ for _, c in utf8gensub(repl) do
+ if not ignore then
+ if c == '%' then
+ ignore = true
+ else
+ ret = ret .. c
+ end
+ else
+ num = tonumber(c)
+ if num then
+ ret = ret .. assert(args[num], "invalid capture index %" .. c)
+ else
+ ret = ret .. c
+ end
+ ignore = false
+ end
+ end
+ elseif type(repl) == 'table' then
+ ret = repl[args[1]] or args[0]
+ elseif type(repl) == 'function' then
+ ret = repl(unpack(args, 1)) or args[0]
+ end
+ return ret
+end
+
+local function utf8gsub(str, regex, repl, limit)
+ limit = limit or -1
+ local subbed = ''
+ local prev_sub_finish = 1
+
+ local func = get_matcher_function(regex, false)
+ local ctx, result, captures
+ local continue_pos = 1
+
+ local n = 0
+ while limit ~= n do
+ ctx, result, captures = func(str, continue_pos, utf8)
+ if not ctx then break end
+
+ utf8.debug('ctx:', ctx)
+ utf8.debug('result:', result)
+ utf8.debug('result:', utf8sub(str, result.start, result.finish))
+ utf8.debug('captures:', captures)
+
+ continue_pos = math.max(result.finish + 1, result.start + 1)
+ local args
+ if #captures > 0 then
+ args = {[0] = utf8sub(str, result.start, result.finish), unpack(captures)}
+ else
+ args = {[0] = utf8sub(str, result.start, result.finish)}
+ args[1] = args[0]
+ end
+
+ subbed = subbed .. utf8sub(str, prev_sub_finish, result.start - 1)
+ subbed = subbed .. replace(repl, args)
+ prev_sub_finish = result.finish + 1
+ n = n + 1
+
+ end
+
+ return subbed .. utf8sub(str, prev_sub_finish), n
+end
+
+-- attaching high-level functions
+utf8.find = utf8find
+utf8.match = utf8match
+utf8.gmatch = utf8gmatch
+utf8.gsub = utf8gsub
+
+return utf8
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/init.lua b/ar/.config/mpv/script-modules/utf8/init.lua
new file mode 100644
index 0000000..d2f72a4
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/init.lua
@@ -0,0 +1,71 @@
+local module_path = ...
+module_path = module_path:match("^(.-)init$") or (module_path .. '.')
+
+local ffi_enabled, ffi = pcall(require, 'ffi')
+
+local utf8 = {
+ config = {},
+ default = {
+ debug = nil,
+ logger = io.write,
+ loadstring = (loadstring or load),
+ unpack = (unpack or table.unpack),
+ cache = {
+ regex = setmetatable({},{
+ __mode = 'kv'
+ }),
+ plain = setmetatable({},{
+ __mode = 'kv'
+ }),
+ },
+ locale = nil,
+ int32array = function(size)
+ if ffi_enabled then
+ return ffi.new("uint32_t[?]", size + 1)
+ else
+ return {}
+ end
+ end,
+ conversion = {
+ uc_lc = nil,
+ lc_uc = nil
+ }
+ },
+ regex = {
+ compiletime = {
+ charclass = {},
+ begins = {},
+ ends = {},
+ modifier = {},
+ }
+ },
+ util = {},
+}
+
+function utf8:require(name)
+ local full_module_path = module_path .. name
+ if package.loaded[full_module_path] then
+ return package.loaded[full_module_path]
+ end
+
+ local mod = require(full_module_path)
+ if type(mod) == 'function' then
+ mod = mod(self)
+ package.loaded[full_module_path] = mod
+ end
+ return mod
+end
+
+function utf8:init()
+ for k, v in pairs(self.default) do
+ self.config[k] = self.config[k] or v
+ end
+
+ self:require "util"
+ self:require "primitives.init"
+ self:require "functions.lua53"
+
+ return self
+end
+
+return utf8
diff --git a/ar/.config/mpv/script-modules/utf8/modifier/compiletime/frontier.lua b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/frontier.lua
new file mode 100644
index 0000000..cf0f4ab
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/frontier.lua
@@ -0,0 +1,50 @@
+return function(utf8)
+
+local matchers = {
+ frontier = function(class, name)
+ local class_name = 'class' .. name
+ return [[
+ local ]] .. class_name .. [[ = ]] .. class .. [[
+
+ add(function(ctx) -- frontier
+ ctx:prev_char()
+ local prev_charcode = ctx:get_charcode() or 0
+ ctx:next_char()
+ local charcode = ctx:get_charcode() or 0
+ -- debug("frontier pos", ctx.pos, "prev_charcode", prev_charcode, "charcode", charcode)
+ if ]] .. class_name .. [[:test(prev_charcode) then return end
+ if ]] .. class_name .. [[:test(charcode) then
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end
+ end)
+]]
+ end,
+ simple = utf8:require("modifier.compiletime.simple").simple,
+}
+
+local function parse(regex, c, bs, ctx)
+ local functions, nbs, class
+
+ if c == '%' then
+ if utf8.raw.sub(regex, bs + 1, bs + 1) ~= 'f' then return end
+ if utf8.raw.sub(regex, bs + 2, bs + 2) ~= '[' then error("missing '[' after '%f' in pattern") end
+
+ functions = {}
+ if ctx.prev_class then
+ table.insert(functions, matchers.simple(ctx.prev_class, tostring(bs)))
+ ctx.prev_class = nil
+ end
+ class, nbs = utf8.regex.compiletime.charclass.parse(regex, '[', bs + 2, ctx)
+ nbs = nbs + 2
+ table.insert(functions, matchers.frontier(class:build(), tostring(bs)))
+ end
+
+ return functions, nbs
+end
+
+return {
+ parse = parse,
+}
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/modifier/compiletime/parser.lua b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/parser.lua
new file mode 100644
index 0000000..9149f71
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/parser.lua
@@ -0,0 +1,20 @@
+return function(utf8)
+
+utf8.config.modifier = utf8.config.modifier or {
+ utf8:require "modifier.compiletime.vanilla",
+ utf8:require "modifier.compiletime.frontier",
+ utf8:require "modifier.compiletime.stub",
+}
+
+function utf8.regex.compiletime.modifier.parse(regex, c, bs, ctx)
+ for _, m in ipairs(utf8.config.modifier) do
+ local functions, move = m.parse(regex, c, bs, ctx)
+ utf8.debug("mod", _, c, bs, move, functions and utf8.config.unpack(functions))
+ if functions then
+ ctx.prev_class = nil
+ return functions, move
+ end
+ end
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/modifier/compiletime/simple.lua b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/simple.lua
new file mode 100644
index 0000000..1a28b85
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/simple.lua
@@ -0,0 +1,23 @@
+return function(utf8)
+
+local matchers = {
+ simple = function(class, name)
+ local class_name = 'class' .. name
+ return [[
+ local ]] .. class_name .. [[ = ]] .. class .. [[
+
+ add(function(ctx) -- simple
+ -- debug(ctx, 'simple', ']] .. class_name .. [[')
+ if ]] .. class_name .. [[:test(ctx:get_charcode()) then
+ ctx:next_char()
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end
+ end)
+]]
+ end,
+}
+
+return matchers
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/modifier/compiletime/stub.lua b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/stub.lua
new file mode 100644
index 0000000..e1289a6
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/stub.lua
@@ -0,0 +1,28 @@
+return function(utf8)
+
+local matchers = utf8:require("modifier.compiletime.simple")
+
+local function parse(regex, c, bs, ctx)
+ local functions
+
+ if ctx.prev_class then
+ functions = { matchers.simple(ctx.prev_class, tostring(bs)) }
+ ctx.prev_class = nil
+ end
+
+ return functions, 0
+end
+
+local function check(ctx)
+ if ctx.prev_class then
+ table.insert(ctx.funcs, matchers.simple(ctx.prev_class, tostring(ctx.pos)))
+ ctx.prev_class = nil
+ end
+end
+
+return {
+ parse = parse,
+ check = check,
+}
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/modifier/compiletime/vanilla.lua b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/vanilla.lua
new file mode 100644
index 0000000..96e79d2
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/modifier/compiletime/vanilla.lua
@@ -0,0 +1,270 @@
+return function(utf8)
+
+local utf8unicode = utf8.byte
+local sub = utf8.raw.sub
+
+local matchers = {
+ star = function(class, name)
+ local class_name = 'class' .. name
+ return [[
+ local ]] .. class_name .. [[ = ]] .. class .. [[
+
+ add(function(ctx) -- star
+ -- debug(ctx, 'star', ']] .. class_name .. [[')
+ local clone = ctx:clone()
+ while ]] .. class_name .. [[:test(clone:get_charcode()) do
+ clone:next_char()
+ end
+ local pos = clone.pos
+ while pos >= ctx.pos do
+ clone.pos = pos
+ clone.func_pos = ctx.func_pos
+ clone:next_function()
+ clone:get_function()(clone)
+ if clone.modified then
+ clone = ctx:clone()
+ end
+ pos = pos - 1
+ end
+ end)
+]]
+ end,
+ minus = function(class, name)
+ local class_name = 'class' .. name
+ return [[
+ local ]] .. class_name .. [[ = ]] .. class .. [[
+
+ add(function(ctx) -- minus
+ -- debug(ctx, 'minus', ']] .. class_name .. [[')
+
+ local clone = ctx:clone()
+ local pos
+ repeat
+ pos = clone.pos
+ clone:next_function()
+ clone:get_function()(clone)
+ if clone.modified then
+ clone = ctx:clone()
+ clone.pos = pos
+ else
+ clone.pos = pos
+ clone.func_pos = ctx.func_pos
+ end
+ local match = ]] .. class_name .. [[:test(clone:get_charcode())
+ clone:next_char()
+ until not match
+ end)
+]]
+ end,
+ question = function(class, name)
+ local class_name = 'class' .. name
+ return [[
+ local ]] .. class_name .. [[ = ]] .. class .. [[
+
+ add(function(ctx) -- question
+ -- debug(ctx, 'question', ']] .. class_name .. [[')
+ local saved = ctx:clone()
+ if ]] .. class_name .. [[:test(ctx:get_charcode()) then
+ ctx:next_char()
+ ctx:next_function()
+ ctx:get_function()(ctx)
+ end
+ ctx = saved
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end)
+]]
+ end,
+ capture_start = function(number)
+ return [[
+ add(function(ctx)
+ ctx.modified = true
+ -- debug(ctx, 'capture_start', ']] .. tostring(number) .. [[')
+ table.insert(ctx.captures.active, { id = ]] .. tostring(number) .. [[, start = ctx.pos })
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end)
+]]
+ end,
+ capture_finish = function(number)
+ return [[
+ add(function(ctx)
+ ctx.modified = true
+ -- debug(ctx, 'capture_finish', ']] .. tostring(number) .. [[')
+ local cap = table.remove(ctx.captures.active)
+ cap.finish = ctx.pos
+ local b, e = ctx.offsets[cap.start], ctx.offsets[cap.finish]
+ if cap.start < 1 then
+ b = 1
+ elseif cap.start >= ctx.len then
+ b = ctx.rawlen + 1
+ end
+ if cap.finish < 1 then
+ e = 1
+ elseif cap.finish >= ctx.len then
+ e = ctx.rawlen + 1
+ end
+ ctx.captures[cap.id] = rawsub(ctx.str, b, e - 1)
+ -- debug('capture#' .. tostring(cap.id), '[' .. tostring(cap.start).. ',' .. tostring(cap.finish) .. ']' , 'is', ctx.captures[cap.id])
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end)
+]]
+ end,
+ capture_position = function(number)
+ return [[
+ add(function(ctx)
+ ctx.modified = true
+ -- debug(ctx, 'capture_position', ']] .. tostring(number) .. [[')
+ ctx.captures[ ]] .. tostring(number) .. [[ ] = ctx.pos
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end)
+]]
+ end,
+ capture = function(number)
+ return [[
+ add(function(ctx)
+ -- debug(ctx, 'capture', ']] .. tostring(number) .. [[')
+ local cap = ctx.captures[ ]] .. tostring(number) .. [[ ]
+ local len = utf8len(cap)
+ local check = utf8sub(ctx.str, ctx.pos, ctx.pos + len - 1)
+ -- debug("capture check:", cap, check)
+ if cap == check then
+ ctx.pos = ctx.pos + len
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end
+ end)
+]]
+ end,
+ balancer = function(pair, name)
+ local class_name = 'class' .. name
+ return [[
+
+ add(function(ctx) -- balancer
+ local d, b = ]] .. tostring(utf8unicode(pair[1])) .. [[, ]] .. tostring(utf8unicode(pair[2])) .. [[
+ if ctx:get_charcode() ~= d then return end
+ local balance = 0
+ repeat
+ local c = ctx:get_charcode()
+ if c == nil then return end
+
+ if c == d then
+ balance = balance + 1
+ elseif c == b then
+ balance = balance - 1
+ end
+ -- debug("balancer: balance=", balance, ", d=", d, ", b=", b, ", charcode=", ctx:get_charcode())
+ ctx:next_char()
+ until balance == 0 or (balance == 2 and d == b)
+ ctx:next_function()
+ return ctx:get_function()(ctx)
+ end)
+]]
+ end,
+ simple = utf8:require("modifier.compiletime.simple").simple,
+}
+
+local next = utf8.util.next
+
+local function parse(regex, c, bs, ctx)
+ local functions, nbs = nil, bs
+ if c == '%' then
+ c, nbs = next(regex, bs)
+ utf8.debug("next", c, bs)
+ if c == '' then
+ error("malformed pattern (ends with '%')")
+ end
+ if utf8.raw.find('123456789', c, 1, true) then
+ functions = { matchers.capture(tonumber(c)) }
+ nbs = utf8.next(regex, nbs)
+ elseif c == 'b' then
+ local d, b
+ d, nbs = next(regex, nbs)
+ b, nbs = next(regex, nbs)
+ assert(d ~= '' and b ~= '', "unbalanced pattern")
+ functions = { matchers.balancer({d, b}, tostring(bs)) }
+ nbs = utf8.next(regex, nbs)
+ end
+
+ if functions and ctx.prev_class then
+ table.insert(functions, 1, matchers.simple(ctx.prev_class, tostring(bs)))
+ end
+ elseif c == '*' and ctx.prev_class then
+ functions = {
+ matchers.star(
+ ctx.prev_class,
+ tostring(bs)
+ )
+ }
+ nbs = bs + 1
+ elseif c == '+' and ctx.prev_class then
+ functions = {
+ matchers.simple(
+ ctx.prev_class,
+ tostring(bs)
+ ),
+ matchers.star(
+ ctx.prev_class,
+ tostring(bs)
+ )
+ }
+ nbs = bs + 1
+ elseif c == '-' and ctx.prev_class then
+ functions = {
+ matchers.minus(
+ ctx.prev_class,
+ tostring(bs)
+ )
+ }
+ nbs = bs + 1
+ elseif c == '?' and ctx.prev_class then
+ functions = {
+ matchers.question(
+ ctx.prev_class,
+ tostring(bs)
+ )
+ }
+ nbs = bs + 1
+ elseif c == '(' then
+ ctx.capture = ctx.capture or {balance = 0, id = 0}
+ ctx.capture.id = ctx.capture.id + 1
+ local nc = next(regex, nbs)
+ if nc == ')' then
+ functions = {matchers.capture_position(ctx.capture.id)}
+ nbs = bs + 2
+ else
+ ctx.capture.balance = ctx.capture.balance + 1
+ functions = {matchers.capture_start(ctx.capture.id)}
+ nbs = bs + 1
+ end
+ if ctx.prev_class then
+ table.insert(functions, 1, matchers.simple(ctx.prev_class, tostring(bs)))
+ end
+ elseif c == ')' then
+ ctx.capture = ctx.capture or {balance = 0, id = 0}
+ functions = { matchers.capture_finish(ctx.capture.id) }
+
+ ctx.capture.balance = ctx.capture.balance - 1
+ assert(ctx.capture.balance >= 0, 'invalid capture: "(" missing')
+
+ if ctx.prev_class then
+ table.insert(functions, 1, matchers.simple(ctx.prev_class, tostring(bs)))
+ end
+ nbs = bs + 1
+ end
+
+ return functions, nbs - bs
+end
+
+local function check(ctx)
+ if ctx.capture then assert(ctx.capture.balance == 0, 'invalid capture: ")" missing') end
+end
+
+return {
+ parse = parse,
+ check = check,
+}
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/primitives/dummy.lua b/ar/.config/mpv/script-modules/utf8/primitives/dummy.lua
new file mode 100644
index 0000000..a4665f5
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/primitives/dummy.lua
@@ -0,0 +1,555 @@
+-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $
+--
+-- Provides UTF-8 aware string functions implemented in pure lua:
+-- * utf8len(s)
+-- * utf8sub(s, i, j)
+-- * utf8reverse(s)
+-- * utf8char(unicode)
+-- * utf8unicode(s, i, j)
+-- * utf8gensub(s, sub_len)
+-- * utf8find(str, regex, init, plain)
+-- * utf8match(str, regex, init)
+-- * utf8gmatch(str, regex, all)
+-- * utf8gsub(str, regex, repl, limit)
+--
+-- All functions behave as their non UTF-8 aware counterparts with the exception
+-- that UTF-8 characters are used instead of bytes for all units.
+
+--[[
+Copyright (c) 2006-2007, Kyle Smith
+All rights reserved.
+
+Contributors:
+ Alimov Stepan
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the author nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--]]
+
+-- ABNF from RFC 3629
+--
+-- UTF8-octets = *( UTF8-char )
+-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+-- UTF8-1 = %x00-7F
+-- UTF8-2 = %xC2-DF UTF8-tail
+-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+-- %xF4 %x80-8F 2( UTF8-tail )
+-- UTF8-tail = %x80-BF
+--
+return function(utf8)
+
+local byte = string.byte
+local char = string.char
+local dump = string.dump
+local find = string.find
+local format = string.format
+local len = string.len
+local lower = string.lower
+local rep = string.rep
+local sub = string.sub
+local upper = string.upper
+
+local utf8charpattern = '[%z\1-\127\194-\244][\128-\191]*'
+
+local function utf8symbollen(byte)
+ return not byte and 0 or (byte < 0x80 and 1) or (byte >= 0xF0 and 4) or (byte >= 0xE0 and 3) or (byte >= 0xC0 and 2) or 1
+end
+
+local head_table = utf8.config.int32array(256)
+for i = 0, 255 do
+ head_table[i] = utf8symbollen(i)
+end
+head_table[256] = 0
+
+local function utf8charbytes(str, bs)
+ return head_table[byte(str, bs) or 256]
+end
+
+local function utf8next(str, bs)
+ return bs + utf8charbytes(str, bs)
+end
+
+-- returns the number of characters in a UTF-8 string
+local function utf8len (str)
+ local bs = 1
+ local bytes = len(str)
+ local length = 0
+
+ while bs <= bytes do
+ length = length + 1
+ bs = utf8next(str, bs)
+ end
+
+ return length
+end
+
+-- functions identically to string.sub except that i and j are UTF-8 characters
+-- instead of bytes
+local function utf8sub (s, i, j)
+ -- argument defaults
+ j = j or -1
+
+ local bs = 1
+ local bytes = len(s)
+ local length = 0
+
+ local l = (i >= 0 and j >= 0) or utf8len(s)
+ i = (i >= 0) and i or l + i + 1
+ j = (j >= 0) and j or l + j + 1
+
+ if i > j then
+ return ""
+ end
+
+ local start, finish = 1, bytes
+
+ while bs <= bytes do
+ length = length + 1
+
+ if length == i then
+ start = bs
+ end
+
+ bs = utf8next(s, bs)
+
+ if length == j then
+ finish = bs - 1
+ break
+ end
+ end
+
+ if i > length then start = bytes + 1 end
+ if j < 1 then finish = 0 end
+
+ return sub(s, start, finish)
+end
+
+-- http://en.wikipedia.org/wiki/Utf8
+-- http://developer.coronalabs.com/code/utf-8-conversion-utility
+local function utf8char(...)
+ local codes = {...}
+ local result = {}
+
+ for _, unicode in ipairs(codes) do
+
+ if unicode <= 0x7F then
+ result[#result + 1] = unicode
+ elseif unicode <= 0x7FF then
+ local b0 = 0xC0 + math.floor(unicode / 0x40);
+ local b1 = 0x80 + (unicode % 0x40);
+ result[#result + 1] = b0
+ result[#result + 1] = b1
+ elseif unicode <= 0xFFFF then
+ local b0 = 0xE0 + math.floor(unicode / 0x1000);
+ local b1 = 0x80 + (math.floor(unicode / 0x40) % 0x40);
+ local b2 = 0x80 + (unicode % 0x40);
+ result[#result + 1] = b0
+ result[#result + 1] = b1
+ result[#result + 1] = b2
+ elseif unicode <= 0x10FFFF then
+ local code = unicode
+ local b3= 0x80 + (code % 0x40);
+ code = math.floor(code / 0x40)
+ local b2= 0x80 + (code % 0x40);
+ code = math.floor(code / 0x40)
+ local b1= 0x80 + (code % 0x40);
+ code = math.floor(code / 0x40)
+ local b0= 0xF0 + code;
+
+ result[#result + 1] = b0
+ result[#result + 1] = b1
+ result[#result + 1] = b2
+ result[#result + 1] = b3
+ else
+ error 'Unicode cannot be greater than U+10FFFF!'
+ end
+
+ end
+
+ return char(utf8.config.unpack(result))
+end
+
+
+local shift_6 = 2^6
+local shift_12 = 2^12
+local shift_18 = 2^18
+
+local utf8unicode
+utf8unicode = function(str, ibs, jbs)
+ if ibs > jbs then return end
+
+ local ch,bytes
+
+ bytes = utf8charbytes(str, ibs)
+ if bytes == 0 then return end
+
+ local unicode
+
+ if bytes == 1 then unicode = byte(str, ibs, ibs) end
+ if bytes == 2 then
+ local byte0,byte1 = byte(str, ibs, ibs + 1)
+ if byte0 and byte1 then
+ local code0,code1 = byte0-0xC0,byte1-0x80
+ unicode = code0*shift_6 + code1
+ else
+ unicode = byte0
+ end
+ end
+ if bytes == 3 then
+ local byte0,byte1,byte2 = byte(str, ibs, ibs + 2)
+ if byte0 and byte1 and byte2 then
+ local code0,code1,code2 = byte0-0xE0,byte1-0x80,byte2-0x80
+ unicode = code0*shift_12 + code1*shift_6 + code2
+ else
+ unicode = byte0
+ end
+ end
+ if bytes == 4 then
+ local byte0,byte1,byte2,byte3 = byte(str, ibs, ibs + 3)
+ if byte0 and byte1 and byte2 and byte3 then
+ local code0,code1,code2,code3 = byte0-0xF0,byte1-0x80,byte2-0x80,byte3-0x80
+ unicode = code0*shift_18 + code1*shift_12 + code2*shift_6 + code3
+ else
+ unicode = byte0
+ end
+ end
+
+ if ibs == jbs then
+ return unicode
+ else
+ return unicode,utf8unicode(str, ibs+bytes, jbs)
+ end
+end
+
+local function utf8byte(str, i, j)
+ if #str == 0 then return end
+
+ local ibs, jbs
+
+ if i or j then
+ i = i or 1
+ j = j or i
+
+ local str_len = utf8len(str)
+ i = i < 0 and str_len + i + 1 or i
+ j = j < 0 and str_len + j + 1 or j
+ j = j > str_len and str_len or j
+
+ if i > j then return end
+
+ for p = 1, i - 1 do
+ ibs = utf8next(str, ibs or 1)
+ end
+
+ if i == j then
+ jbs = ibs
+ else
+ for p = 1, j - 1 do
+ jbs = utf8next(str, jbs or 1)
+ end
+ end
+
+ if not ibs or not jbs then
+ return nil
+ end
+ else
+ ibs, jbs = 1, 1
+ end
+
+ return utf8unicode(str, ibs, jbs)
+end
+
+local function utf8gensub(str, sub_len)
+ sub_len = sub_len or 1
+ local max_len = #str
+ return function(skip_ptr, bs)
+ bs = (bs and bs or 1) + (skip_ptr and (skip_ptr[1] or 0) or 0)
+
+ local nbs = bs
+ if bs > max_len then return nil end
+ for i = 1, sub_len do
+ nbs = utf8next(str, nbs)
+ end
+
+ return nbs, sub(str, bs, nbs - 1), bs
+ end
+end
+
+local function utf8reverse (s)
+ local result = ''
+ for _, w in utf8gensub(s) do result = w .. result end
+ return result
+end
+
+local function utf8validator(str, bs)
+ bs = bs or 1
+
+ if type(str) ~= "string" then
+ error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(str).. ")")
+ end
+ if type(bs) ~= "number" then
+ error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(bs).. ")")
+ end
+
+ local c = byte(str, bs)
+ if not c then return end
+
+ -- determine bytes needed for character, based on RFC 3629
+
+ -- UTF8-1
+ if c >= 0 and c <= 127 then
+ return bs + 1
+ elseif c >= 128 and c <= 193 then
+ return bs + 1, bs, 1, c
+ -- UTF8-2
+ elseif c >= 194 and c <= 223 then
+ local c2 = byte(str, bs + 1)
+ if not c2 or c2 < 128 or c2 > 191 then
+ return bs + 2, bs, 2, c2
+ end
+
+ return bs + 2
+ -- UTF8-3
+ elseif c >= 224 and c <= 239 then
+ local c2 = byte(str, bs + 1)
+
+ if not c2 then
+ return bs + 2, bs, 2, c2
+ end
+
+ -- validate byte 2
+ if c == 224 and (c2 < 160 or c2 > 191) then
+ return bs + 2, bs, 2, c2
+ elseif c == 237 and (c2 < 128 or c2 > 159) then
+ return bs + 2, bs, 2, c2
+ elseif c2 < 128 or c2 > 191 then
+ return bs + 2, bs, 2, c2
+ end
+
+ local c3 = byte(str, bs + 2)
+ if not c3 or c3 < 128 or c3 > 191 then
+ return bs + 3, bs, 3, c3
+ end
+
+ return bs + 3
+ -- UTF8-4
+ elseif c >= 240 and c <= 244 then
+ local c2 = byte(str, bs + 1)
+
+ if not c2 then
+ return bs + 2, bs, 2, c2
+ end
+
+ -- validate byte 2
+ if c == 240 and (c2 < 144 or c2 > 191) then
+ return bs + 2, bs, 2, c2
+ elseif c == 244 and (c2 < 128 or c2 > 143) then
+ return bs + 2, bs, 2, c2
+ elseif c2 < 128 or c2 > 191 then
+ return bs + 2, bs, 2, c2
+ end
+
+ local c3 = byte(str, bs + 2)
+ if not c3 or c3 < 128 or c3 > 191 then
+ return bs + 3, bs, 3, c3
+ end
+
+ local c4 = byte(str, bs + 3)
+ if not c4 or c4 < 128 or c4 > 191 then
+ return bs + 4, bs, 4, c4
+ end
+
+ return bs + 4
+ else -- c > 245
+ return bs + 1, bs, 1, c
+ end
+end
+
+local function utf8validate(str, byte_pos)
+ local result = {}
+ for nbs, bs, part, code in utf8validator, str, byte_pos do
+ if bs then
+ result[#result + 1] = { pos = bs, part = part, code = code }
+ end
+ end
+ return #result == 0, result
+end
+
+local function utf8codes(str)
+ local max_len = #str
+ local bs = 1
+ return function(skip_ptr)
+ if bs > max_len then return nil end
+ local pbs = bs
+ bs = utf8next(str, pbs)
+
+ return pbs, utf8unicode(str, pbs, pbs), pbs
+ end
+end
+
+
+--[[--
+differs from Lua 5.3 utf8.offset in accepting any byte positions (not only head byte) for all n values
+
+h - head, c - continuation, t - tail
+hhhccthccthccthcthhh
+ ^ start byte pos
+searching current charracter head by moving backwards
+hhhccthccthccthcthhh
+ ^ head
+
+n == 0: current position
+n > 0: n jumps forward
+n < 0: n more scans backwards
+--]]--
+local function utf8offset(str, n, bs)
+ local l = #str
+ if not bs then
+ if n < 0 then
+ bs = l + 1
+ else
+ bs = 1
+ end
+ end
+ if bs <= 0 or bs > l + 1 then
+ error("bad argument #3 to 'offset' (position out of range)")
+ end
+
+ if n == 0 then
+ if bs == l + 1 then
+ return bs
+ end
+ while true do
+ local b = byte(str, bs)
+ if (0 < b and b < 127)
+ or (194 < b and b < 244) then
+ return bs
+ end
+ bs = bs - 1
+ if bs < 1 then
+ return
+ end
+ end
+ elseif n < 0 then
+ bs = bs - 1
+ repeat
+ if bs < 1 then
+ return
+ end
+
+ local b = byte(str, bs)
+ if (0 < b and b < 127)
+ or (194 < b and b < 244) then
+ n = n + 1
+ end
+ bs = bs - 1
+ until n == 0
+ return bs + 1
+ else
+ while true do
+ if bs > l then
+ return
+ end
+
+ local b = byte(str, bs)
+ if (0 < b and b < 127)
+ or (194 < b and b < 244) then
+ n = n - 1
+ for i = 1, n do
+ if bs > l then
+ return
+ end
+ bs = utf8next(str, bs)
+ end
+ return bs
+ end
+ bs = bs - 1
+ end
+ end
+
+end
+
+local function utf8replace (s, mapping)
+ if type(s) ~= "string" then
+ error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")")
+ end
+ if type(mapping) ~= "table" then
+ error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")")
+ end
+ local result = utf8.raw.gsub( s, utf8charpattern, mapping )
+ return result
+end
+
+local function utf8upper (s)
+ return utf8replace(s, utf8.config.conversion.lc_uc)
+end
+
+if utf8.config.conversion.lc_uc then
+ upper = utf8upper
+end
+
+local function utf8lower (s)
+ return utf8replace(s, utf8.config.conversion.uc_lc)
+end
+
+if utf8.config.conversion.uc_lc then
+ lower = utf8lower
+end
+
+utf8.len = utf8len
+utf8.sub = utf8sub
+utf8.reverse = utf8reverse
+utf8.char = utf8char
+utf8.unicode = utf8unicode
+utf8.byte = utf8byte
+utf8.next = utf8next
+utf8.gensub = utf8gensub
+utf8.validator = utf8validator
+utf8.validate = utf8validate
+utf8.dump = dump
+utf8.format = format
+utf8.lower = lower
+utf8.upper = upper
+utf8.rep = rep
+utf8.raw = {}
+for k,v in pairs(string) do
+ utf8.raw[k] = v
+end
+
+utf8.charpattern = utf8charpattern
+utf8.offset = utf8offset
+if _VERSION == 'Lua 5.3' then
+ local utf8_53 = require "utf8"
+ utf8.codes = utf8_53.codes
+ utf8.codepoint = utf8_53.codepoint
+ utf8.len53 = utf8_53.len
+else
+ utf8.codes = utf8codes
+ utf8.codepoint = utf8unicode
+end
+
+return utf8
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/primitives/init.lua b/ar/.config/mpv/script-modules/utf8/primitives/init.lua
new file mode 100644
index 0000000..df28ef3
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/primitives/init.lua
@@ -0,0 +1,23 @@
+return function(utf8)
+
+local provided = utf8.config.primitives
+
+if provided then
+ if type(provided) == "table" then
+ return provided
+ elseif type(provided) == "function" then
+ return provided(utf8)
+ else
+ return utf8:require(provided)
+ end
+end
+
+if pcall(require, "tarantool") then
+ return utf8:require "primitives.tarantool"
+elseif pcall(require, "ffi") then
+ return utf8:require "primitives.native"
+else
+ return utf8:require "primitives.dummy"
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/primitives/native.lua b/ar/.config/mpv/script-modules/utf8/primitives/native.lua
new file mode 100644
index 0000000..c9aca54
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/primitives/native.lua
@@ -0,0 +1,57 @@
+return function(utf8)
+
+local ffi = require("ffi")
+if ffi.os == "Windows" then
+ os.setlocale(utf8.config.locale or "english_us.65001", "ctype")
+ ffi.cdef[[
+ short towupper(short c);
+ short towlower(short c);
+ ]]
+else
+ os.setlocale(utf8.config.locale or "C.UTF-8", "ctype")
+ ffi.cdef[[
+ int towupper(int c);
+ int towlower(int c);
+ ]]
+end
+
+utf8:require "primitives.dummy"
+
+if not utf8.config.conversion.uc_lc then
+ function utf8.lower(str)
+ local bs = 1
+ local nbs
+ local bytes = utf8.raw.len(str)
+ local res = {}
+
+ while bs <= bytes do
+ nbs = utf8.next(str, bs)
+ local cp = utf8.unicode(str, bs, nbs)
+ res[#res + 1] = ffi.C.towlower(cp)
+ bs = nbs
+ end
+
+ return utf8.char(utf8.config.unpack(res))
+ end
+end
+
+if not utf8.config.conversion.lc_uc then
+ function utf8.upper(str)
+ local bs = 1
+ local nbs
+ local bytes = utf8.raw.len(str)
+ local res = {}
+
+ while bs <= bytes do
+ nbs = utf8.next(str, bs)
+ local cp = utf8.unicode(str, bs, nbs)
+ res[#res + 1] = ffi.C.towupper(cp)
+ bs = nbs
+ end
+
+ return utf8.char(utf8.config.unpack(res))
+ end
+end
+
+return utf8
+end
diff --git a/ar/.config/mpv/script-modules/utf8/primitives/tarantool.lua b/ar/.config/mpv/script-modules/utf8/primitives/tarantool.lua
new file mode 100644
index 0000000..c38acf6
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/primitives/tarantool.lua
@@ -0,0 +1,13 @@
+return function(utf8)
+
+utf8:require "primitives.dummy"
+
+local tnt_utf8 = utf8.config.tarantool_utf8 or require("utf8")
+
+utf8.lower = tnt_utf8.lower
+utf8.upper = tnt_utf8.upper
+utf8.len = tnt_utf8.len
+utf8.char = tnt_utf8.char
+
+return utf8
+end
diff --git a/ar/.config/mpv/script-modules/utf8/regex_parser.lua b/ar/.config/mpv/script-modules/utf8/regex_parser.lua
new file mode 100644
index 0000000..3190f1b
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/regex_parser.lua
@@ -0,0 +1,80 @@
+return function(utf8)
+
+utf8:require "modifier.compiletime.parser"
+utf8:require "charclass.compiletime.parser"
+utf8:require "begins.compiletime.parser"
+utf8:require "ends.compiletime.parser"
+
+local gensub = utf8.gensub
+local sub = utf8.sub
+
+local parser_context = utf8:require "context.compiletime"
+
+return function(regex, plain)
+ utf8.debug("regex", regex)
+ local ctx = parser_context:new()
+
+ local skip = {0}
+ for nbs, c, bs in gensub(regex, 0), skip do
+ repeat -- continue
+ skip[1] = 0
+
+ c = utf8.raw.sub(regex, bs, utf8.next(regex, bs) - 1)
+
+ local functions, move = utf8.regex.compiletime.begins.parse(regex, c, bs, ctx)
+ if functions then
+ ctx.begins = functions
+ skip[1] = move
+ end
+ if skip[1] ~= 0 then break end
+
+ local functions, move = utf8.regex.compiletime.ends.parse(regex, c, bs, ctx)
+ if functions then
+ ctx.ends = functions
+ skip[1] = move
+ end
+ if skip[1] ~= 0 then break end
+
+ local functions, move = utf8.regex.compiletime.modifier.parse(regex, c, bs, ctx)
+ if functions then
+ for _, f in ipairs(functions) do
+ ctx.funcs[#ctx.funcs + 1] = f
+ end
+ skip[1] = move
+ end
+ if skip[1] ~= 0 then break end
+
+ local charclass, move = utf8.regex.compiletime.charclass.parse(regex, c, bs, ctx)
+ if charclass then skip[1] = move end
+ until true -- continue
+ end
+
+ for _, m in ipairs(utf8.config.modifier) do
+ if m.check then m.check(ctx) end
+ end
+
+ local src = [[
+ return function(str, init, utf8)
+ local ctx = utf8:require("context.runtime").new({str = str, pos = init or 1})
+ local cl = utf8:require("charclass.runtime.init")
+ local utf8sub = utf8.sub
+ local rawsub = utf8.raw.sub
+ local utf8len = utf8.len
+ local utf8next = utf8.next
+ local debug = utf8.debug
+ local function add(fun)
+ ctx.functions[#ctx.functions + 1] = fun
+ end
+ ]] .. ctx.begins
+ for _, v in ipairs(ctx.funcs) do src = src .. v end
+ src = src .. ctx.ends .. [[
+ return coroutine.wrap(ctx:get_function())(ctx)
+ end
+ ]]
+
+ utf8.debug(regex, src)
+
+ return assert(utf8.config.loadstring(src, (plain and "plain " or "") .. regex))()
+end
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8/test.sh b/ar/.config/mpv/script-modules/utf8/test.sh
new file mode 100755
index 0000000..b8d2d63
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -xe
+
+lua53=$(which lua5.3 || which true)
+lua51=$(which lua5.1 || which true)
+luajit=$(which luajit || which true)
+
+for test in \
+ test/charclass_compiletime.lua \
+ test/charclass_runtime.lua \
+ test/context_runtime.lua \
+ test/test.lua \
+ test/test_compat.lua \
+ test/test_pm.lua \
+ test/test_utf8data.lua
+do
+ $lua53 $test
+ $lua51 $test
+ $luajit $test
+done
+
+echo "tests passed"
diff --git a/ar/.config/mpv/script-modules/utf8/test/charclass_compiletime.lua b/ar/.config/mpv/script-modules/utf8/test/charclass_compiletime.lua
new file mode 100644
index 0000000..05d762d
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/charclass_compiletime.lua
@@ -0,0 +1,165 @@
+local utf8 = require "init"
+utf8.config = {
+ debug = nil,
+-- debug = utf8:require("util").debug,
+}
+utf8:init()
+
+local ctx = utf8:require("context.compiletime"):new()
+
+local equals = require 'test.util'.equals
+local assert = require 'test.util'.assert
+local assert_equals = require 'test.util'.assert_equals
+local parse = utf8.regex.compiletime.charclass.parse
+
+assert_equals({parse("aabb", "a", 1, ctx)}, {{codes = {utf8.byte("a")}}, 1})
+assert_equals({parse("aabb", "a", 2, ctx)}, {{codes = {utf8.byte("a")}}, 1})
+assert_equals({parse("aabb", "b", 3, ctx)}, {{codes = {utf8.byte("b")}}, 1})
+assert_equals({parse("aabb", "b", 4, ctx)}, {{codes = {utf8.byte("b")}}, 1})
+
+assert_equals({parse("aa%ab", "%", 3, ctx)}, {{classes = {'alpha'}}, 2})
+assert_equals({parse("aac%Ab", "%", 4, ctx)}, {{not_classes = {'alpha'}}, 2})
+assert_equals({parse("aa.b", ".", 3, ctx)}, {{inverted = true}, 1})
+
+assert_equals({parse("aa[c]b", "[", 3, ctx)}, {
+ {codes = {utf8.byte("c")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[c]")
+})
+
+assert_equals({parse("aa[%A]b", "[", 3, ctx)}, {
+ {codes = nil, ranges = nil, classes = nil, not_classes = {'alpha'}},
+ utf8.raw.len("[%A]")
+})
+
+assert_equals({parse("[^%p%d%s%c]+", "[", 1, ctx)}, {
+ {codes = nil, ranges = nil, classes = {'punct', 'digit', 'space', 'cntrl'}, not_classes = nil, inverted = true},
+ utf8.raw.len("[^%p%d%s%c]")
+})
+
+assert_equals({parse("aa[[c]]b", "[", 3, ctx)}, {
+ {codes = {utf8.byte("["), utf8.byte("c")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[[c]")
+})
+
+assert_equals({parse("aa[%a[c]]b", "[", 3, ctx)}, {
+ {codes = {utf8.byte("["), utf8.byte("c")}, ranges = nil, classes = {'alpha'}, not_classes = nil},
+ utf8.raw.len("[%a[c]")
+})
+
+assert_equals({parse("aac-db", "c", 3, ctx)}, {
+ {codes = {utf8.byte("c")}},
+ utf8.raw.len("c")
+})
+
+assert_equals({parse("aa[c-d]b", "[", 3, ctx)}, {
+ {codes = nil, ranges = {{utf8.byte("c"),utf8.byte("d")}}, classes = nil, not_classes = nil},
+ utf8.raw.len("[c-d]")
+})
+assert_equals(ctx.internal, false)
+
+assert_equals({parse("aa[c-]]b", "[", 3, ctx)}, {
+ {codes = {utf8.byte("-"), utf8.byte("c")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[c-]")
+})
+assert_equals(ctx.internal, false)
+
+assert_equals({parse("aad-", "d", 3, ctx)}, {
+ {codes = {utf8.byte("d")}},
+ utf8.raw.len("d")
+})
+assert_equals(ctx.internal, false)
+
+ctx.internal = false
+assert_equals({parse(".", ".", 1, ctx)}, {
+ {inverted = true},
+ utf8.raw.len(".")
+})
+
+assert_equals({parse("[.]", "[", 1, ctx)}, {
+ {codes = {utf8.byte(".")}},
+ utf8.raw.len("[.]")
+})
+
+assert_equals({parse("%?", "%", 1, ctx)}, {
+ {codes = {utf8.byte("?")}},
+ utf8.raw.len("%?")
+})
+
+assert_equals({parse("[]]", "[", 1, ctx)}, {
+ {codes = {utf8.byte("]")}},
+ utf8.raw.len("[]]")
+})
+
+assert_equals({parse("[^]]", "[", 1, ctx)}, {
+ {codes = {utf8.byte("]")}, inverted = true},
+ utf8.raw.len("[^]]")
+})
+
+--[[--
+multibyte chars
+--]]--
+
+assert_equals({parse("ббюю", "б", #"" + 1, ctx)}, {{codes = {utf8.byte("б")}}, utf8.raw.len("б")})
+assert_equals({parse("ббюю", "б", #"б" + 1, ctx)}, {{codes = {utf8.byte("б")}}, utf8.raw.len("б")})
+assert_equals({parse("ббюю", "ю", #"бб" + 1, ctx)}, {{codes = {utf8.byte("ю")}}, utf8.raw.len("ю")})
+assert_equals({parse("ббюю", "ю", #"ббю" + 1, ctx)}, {{codes = {utf8.byte("ю")}}, utf8.raw.len("ю")})
+
+assert_equals({parse("бб%aю", "%", #"бб" + 1, ctx)}, {{classes = {'alpha'}}, 2})
+assert_equals({parse("ббц%Aю", "%", #"ббц" + 1, ctx)}, {{not_classes = {'alpha'}}, 2})
+assert_equals({parse("бб.ю", ".", #"бб" + 1, ctx)}, {{inverted = true}, 1})
+
+assert_equals({parse("бб[ц]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = {utf8.byte("ц")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[ц]")
+})
+
+assert_equals({parse("бб[%A]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = nil, ranges = nil, classes = nil, not_classes = {'alpha'}},
+ utf8.raw.len("[%A]")
+})
+
+assert_equals({parse("бб[[ц]]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = {utf8.byte("["), utf8.byte("ц")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[[ц]")
+})
+
+assert_equals({parse("бб[%a[ц]]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = {utf8.byte("["), utf8.byte("ц")}, ranges = nil, classes = {'alpha'}, not_classes = nil},
+ utf8.raw.len("[%a[ц]")
+})
+
+ctx.internal = true
+assert_equals({parse("ббц-ыю", "ц", #"бб" + 1, ctx)}, {
+ {ranges = {{utf8.byte("ц"),utf8.byte("ы")}}},
+ utf8.raw.len("ц-ы")
+})
+
+ctx.internal = false
+assert_equals({parse("бб[ц-ы]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = nil, ranges = {{utf8.byte("ц"),utf8.byte("ы")}}, classes = nil, not_classes = nil},
+ utf8.raw.len("[ц-ы]")
+})
+
+assert_equals({parse("бб[ц-]]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = {utf8.byte("-"), utf8.byte("ц")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[ц-]")
+})
+
+assert_equals({parse("ббы-", "ы", #"бб" + 1, ctx)}, {
+ {codes = {utf8.byte("ы")}},
+ utf8.raw.len("ы")
+})
+
+ctx.internal = true
+assert_equals({parse("ббы-цю", "ы", #"бб" + 1, ctx)}, {
+ {ranges = {{utf8.byte("ы"),utf8.byte("ц")}}},
+ utf8.raw.len("ы-ц")
+})
+
+ctx.internal = false
+assert_equals({parse("бб[ы]ю", "[", #"бб" + 1, ctx)}, {
+ {codes = {utf8.byte("ы")}, ranges = nil, classes = nil, not_classes = nil},
+ utf8.raw.len("[ы]")
+})
+
+print "OK"
diff --git a/ar/.config/mpv/script-modules/utf8/test/charclass_runtime.lua b/ar/.config/mpv/script-modules/utf8/test/charclass_runtime.lua
new file mode 100644
index 0000000..616af14
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/charclass_runtime.lua
@@ -0,0 +1,116 @@
+local utf8 = require("init")
+utf8.config = {
+ debug = nil, --utf8:require("util").debug
+}
+utf8:init()
+
+local cl = utf8:require("charclass.runtime.init")
+
+local equals = require('test.util').equals
+local assert = require('test.util').assert
+local assert_equals = require('test.util').assert_equals
+
+assert_equals(true, cl.new()
+ :with_codes(utf8.byte' ')
+ :invert()
+ :in_codes(utf8.byte' '))
+
+assert_equals(false, cl.new()
+ :with_codes(utf8.byte' ')
+ :invert()
+ :test(utf8.byte' '))
+
+assert_equals(false, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes('space')
+ :without_classes()
+ :with_subs()
+ :invert()
+ :test(utf8.byte(' ')))
+
+assert_equals(true, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes()
+ :without_classes('space')
+ :with_subs()
+ :invert()
+ :test(utf8.byte(' ')))
+
+assert_equals(false, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes()
+ :without_classes()
+ :with_subs(cl.new():with_classes('space'))
+ :invert()
+ :test(utf8.byte(' ')))
+
+assert_equals(true, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes()
+ :without_classes()
+ :with_subs(cl.new():with_classes('space'):invert())
+ :invert()
+ :test(utf8.byte(' ')))
+
+assert_equals(true, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes('punct', 'digit', 'space', 'cntrl')
+ :without_classes()
+ :with_subs()
+ :invert()
+ :test(utf8.byte'П')
+)
+
+assert_equals(true, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes('punct', 'digit', 'space', 'cntrl')
+ :without_classes()
+ :with_subs()
+ :invert()
+ :test(utf8.byte'и')
+)
+
+assert_equals(true, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes()
+ :without_classes('space')
+ :with_subs()
+ :test(utf8.byte'f')
+)
+
+assert_equals(false, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes()
+ :without_classes('space')
+ :with_subs()
+ :test(utf8.byte'\n')
+)
+
+assert_equals(false, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes('lower')
+ :without_classes()
+ :with_subs()
+ :invert()
+ :test(nil)
+)
+
+assert_equals(false, cl.new()
+ :with_codes()
+ :with_ranges()
+ :with_classes('lower')
+ :without_classes()
+ :with_subs()
+ :test(nil)
+)
+
+print "OK"
diff --git a/ar/.config/mpv/script-modules/utf8/test/context_runtime.lua b/ar/.config/mpv/script-modules/utf8/test/context_runtime.lua
new file mode 100644
index 0000000..9a177bf
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/context_runtime.lua
@@ -0,0 +1,82 @@
+local utf8 = require("init"):init()
+
+local context = utf8:require('context.runtime')
+
+local equals = require('test.util').equals
+local assert = require('test.util').assert
+local assert_equals = require('test.util').assert_equals
+
+local ctx_en
+local ctx_ru
+local function setup()
+ ctx_en = context.new({str = 'asdf'})
+ ctx_ru = context.new({str = 'фыва'})
+end
+
+local test_get_char = (function()
+ setup()
+
+ assert_equals('a', ctx_en:get_char())
+ assert_equals('ф', ctx_ru:get_char())
+end)()
+
+local test_get_charcode = (function()
+ setup()
+
+ assert_equals(utf8.byte'a', ctx_en:get_charcode())
+ assert_equals(utf8.byte'ф', ctx_ru:get_charcode())
+end)()
+
+local test_next_char = (function()
+ setup()
+
+ assert_equals(1, ctx_en.pos)
+ assert_equals(1, ctx_ru.pos)
+
+ ctx_ru:next_char()
+ ctx_en:next_char()
+
+ assert_equals(2, ctx_en.pos)
+ assert_equals(2, ctx_ru.pos)
+
+ assert_equals('s', ctx_en:get_char())
+ assert_equals('ы', ctx_ru:get_char())
+ assert_equals(utf8.byte's', ctx_en:get_charcode())
+ assert_equals(utf8.byte'ы', ctx_ru:get_charcode())
+end)()
+
+local test_clone = (function()
+ setup()
+
+ local clone = ctx_en:clone()
+
+ assert(getmetatable(clone) == getmetatable(ctx_en))
+ assert_equals(clone, ctx_en)
+
+ ctx_en:next_char()
+
+ assert_equals('a', clone:get_char())
+ assert_equals('s', ctx_en:get_char())
+
+end)()
+
+local test_last_char = (function()
+ ctx_en = context.new({str = 'asdf', pos = 4})
+ ctx_ru = context.new({str = 'фыва', pos = 4})
+
+ assert_equals('f', ctx_en:get_char())
+ assert_equals('а', ctx_ru:get_char())
+
+ ctx_ru:next_char()
+ ctx_en:next_char()
+
+ assert_equals(5, ctx_en.pos)
+ assert_equals(5, ctx_ru.pos)
+
+ assert_equals("", ctx_en:get_char())
+ assert_equals("", ctx_ru:get_char())
+ assert_equals(nil, ctx_en:get_charcode())
+ assert_equals(nil, ctx_ru:get_charcode())
+end)()
+
+print('OK')
diff --git a/ar/.config/mpv/script-modules/utf8/test/strict.lua b/ar/.config/mpv/script-modules/utf8/test/strict.lua
new file mode 100644
index 0000000..7324644
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/strict.lua
@@ -0,0 +1,42 @@
+--[[--
+strict.lua from http://metalua.luaforge.net/src/lib/strict.lua.html
+--]]--
+
+--
+-- strict.lua
+-- checks uses of undeclared global variables
+-- All global variables must be 'declared' through a regular assignment
+-- (even assigning nil will do) in a main chunk before being used
+-- anywhere or assigned to inside a function.
+--
+
+local mt = getmetatable(_G)
+if mt == nil then
+ mt = {}
+ setmetatable(_G, mt)
+end
+
+__STRICT = true
+mt.__declared = {}
+
+mt.__newindex = function (t, n, v)
+ if __STRICT and not mt.__declared[n] then
+ local w = debug.getinfo(2, "S").what
+ if w ~= "main" and w ~= "C" then
+ error("assign to undeclared variable '"..n.."'", 2)
+ end
+ mt.__declared[n] = true
+ end
+ rawset(t, n, v)
+end
+
+mt.__index = function (t, n)
+ if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then
+ error("variable '"..n.."' is not declared", 2)
+ end
+ return rawget(t, n)
+end
+
+function global(...)
+ for _, v in ipairs{...} do mt.__declared[v] = true end
+end
diff --git a/ar/.config/mpv/script-modules/utf8/test/test.lua b/ar/.config/mpv/script-modules/utf8/test/test.lua
new file mode 100644
index 0000000..8653b5d
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/test.lua
@@ -0,0 +1,205 @@
+local utf8 = require('init')
+utf8.config = {
+ debug = nil,
+-- debug = utf8:require("util").debug,
+}
+utf8:init()
+
+for k,v in pairs(utf8) do
+ string[k] = v
+end
+
+local LUA_51, LUA_53 = false, false
+if "\xe4" == "xe4" then -- lua5.1
+ LUA_51 = true
+else -- luajit lua5.3
+ LUA_53 = true
+end
+
+local FFI_ENABLED = false
+if pcall(require, "ffi") then
+ FFI_ENABLED = true
+end
+
+local res = {}
+
+local equals = require 'test.util'.equals
+local assert = require 'test.util'.assert
+local assert_equals = require 'test.util'.assert_equals
+
+if FFI_ENABLED then
+ assert_equals(("АБВ"):lower(), "абв")
+ assert_equals(("абв"):upper(), "АБВ")
+end
+
+res = {}
+for _, w in ("123456789"):gensub(2), {1} do res[#res + 1] = w end
+assert_equals({"23", "56", "89"}, res)
+
+assert_equals(0, ("фыва"):next(0))
+assert_equals(100, ("фыва"):next(100))
+assert_equals(#"ф" + 1, ("фыва"):next(1))
+assert_equals("ыва", utf8.raw.sub("фыва", ("фыва"):next(1)))
+
+res = {}
+for p, c in ("абвгд"):codes() do res[#res + 1] = {p, c} end
+assert_equals({
+ {1, utf8.byte'а'},
+ {#'а' + 1, utf8.byte'б'},
+ {#'аб' + 1, utf8.byte'в'},
+ {#'абв' + 1, utf8.byte'г'},
+ {#'абвг' + 1, utf8.byte'д'},
+}, res)
+
+assert_equals(1, utf8.offset('abcde', 0))
+
+assert_equals(1, utf8.offset('abcde', 1))
+assert_equals(5, utf8.offset('abcde', 5))
+assert_equals(6, utf8.offset('abcde', 6))
+assert_equals(nil, utf8.offset('abcde', 7))
+
+assert_equals(5, utf8.offset('abcde', -1))
+assert_equals(1, utf8.offset('abcde', -5))
+assert_equals(nil, utf8.offset('abcde', -6))
+
+assert_equals(1, utf8.offset('abcde', 0, 1))
+assert_equals(3, utf8.offset('abcde', 0, 3))
+assert_equals(6, utf8.offset('abcde', 0, 6))
+
+assert_equals(3, utf8.offset('abcde', 1, 3))
+assert_equals(5, utf8.offset('abcde', 3, 3))
+assert_equals(6, utf8.offset('abcde', 4, 3))
+assert_equals(nil, utf8.offset('abcde', 5, 3))
+
+assert_equals(2, utf8.offset('abcde', -1, 3))
+assert_equals(1, utf8.offset('abcde', -2, 3))
+assert_equals(5, utf8.offset('abcde', -1, 6))
+assert_equals(nil, utf8.offset('abcde', -3, 3))
+
+assert_equals(1, utf8.offset('абвгд', 0))
+
+assert_equals(1, utf8.offset('абвгд', 1))
+assert_equals(#'абвг' + 1, utf8.offset('абвгд', 5))
+assert_equals(#'абвгд' + 1, utf8.offset('абвгд', 6))
+assert_equals(nil, utf8.offset('абвгд', 7))
+
+assert_equals(#'абвг' + 1, utf8.offset('абвгд', -1))
+assert_equals(1, utf8.offset('абвгд', -5))
+assert_equals(nil, utf8.offset('абвгд', -6))
+
+assert_equals(1, utf8.offset('абвгд', 0, 1))
+assert_equals(1, utf8.offset('абвгд', 0, 2))
+assert_equals(#'аб' + 1, utf8.offset('абвгд', 0, #'аб' + 1))
+assert_equals(#'аб' + 1, utf8.offset('абвгд', 0, #'аб' + 2))
+assert_equals(#'абвгд' + 1, utf8.offset('абвгд', 0, #'абвгд' + 1))
+
+assert_equals(#'аб' + 1, utf8.offset('абвгд', 1, #'аб' + 1))
+assert_equals(#'абвг' + 1, utf8.offset('абвгд', 3, #'аб' + 1))
+assert_equals(#'абвгд' + 1, utf8.offset('абвгд', 4, #'аб' + 1))
+assert_equals(#'абвгд' + 1, utf8.offset('абвгд', 4, #'аб' + 2))
+assert_equals(nil, utf8.offset('абвгд', 5, #'аб' + 1))
+
+assert_equals(#'а' + 1, utf8.offset('абвгд', -1, #'аб' + 1))
+assert_equals(1, utf8.offset('абвгд', -2, #'аб' + 1))
+assert_equals(#'абвг' + 1, utf8.offset('абвгд', -1, #'абвгд' + 1))
+assert_equals(nil, utf8.offset('абвгд', -3, #'аб' + 1))
+
+assert(("фыва"):validate())
+assert_equals({false, {{ pos = #"ф" + 1, part = 1, code = 255 }} }, {("ф\255ыва"):validate()})
+if LUA_53 then
+ assert_equals({false, {{ pos = #"ф" + 1, part = 1, code = 0xFF }} }, {("ф\xffыва"):validate()})
+end
+
+assert_equals(nil, ("aabb"):find("%bcd"))
+assert_equals({1, 4}, {("aabb"):find("%bab")})
+assert_equals({1, 2}, {("aba"):find('%bab')})
+
+res = {}
+for w in ("aacaabbcabbacbaacab"):gmatch('%bab') do res[#res + 1] = w end
+assert_equals({"acaabbcabb", "acb", "ab"}, res)
+
+assert_equals({1, 0}, {("aacaabbcabbacbaacab"):find('%f[acb]')})
+assert_equals("a", ("aba"):match('%f[ab].'))
+
+res = {}
+for w in ("aacaabbcabbacbaacab"):gmatch('%f[ab]') do res[#res + 1] = w end
+assert_equals({"", "", "", "", ""}, res)
+
+assert_equals({"HaacHaabbcHabbacHbaacHab", 5}, {("aacaabbcabbacbaacab"):gsub('%f[ab]', 'H')})
+
+res = {}
+for w in ("Привет, мир, от Lua"):gmatch("[^%p%d%s%c]+") do res[#res + 1] = w end
+assert_equals({"Привет", "мир", "от", "Lua"}, res)
+
+res = {}
+for k, v in ("从=世界, 到=Lua"):gmatch("([^%p%s%c]+)=([^%p%s%c]+)") do res[k] = v end
+assert_equals({["到"] = "Lua", ["从"] = "世界"}, res)
+
+assert_equals("Ahoj Ahoj světe světe", ("Ahoj světe"):gsub("([^%p%s%c]+)", "%1 %1"))
+
+assert_equals("Ahoj Ahoj světe", ("Ahoj světe"):gsub("[^%p%s%c]+", "%0 %0", 1))
+
+assert_equals("κόσμο γεια Lua από", ("γεια κόσμο από Lua"):gsub("([^%p%s%c]+)%s*([^%p%s%c]+)", "%2 %1"))
+
+assert_equals({8, 27, "ололоо я водитель э"}, {("пыщпыщ ололоо я водитель энло"):find("(.л.+)н")})
+
+assert_equals({"пыщпыщ о보라보라 я водитель эн보라", 3}, {("пыщпыщ ололоо я водитель энло"):gsub("ло+", "보라")})
+
+assert_equals("пыщпыщ ололоо я", ("пыщпыщ ололоо я водитель энло"):match("^п[лопыщ ]*я"))
+
+assert_equals("в", ("пыщпыщ ололоо я водитель энло"):match("[в-д]+"))
+
+assert_equals(nil, ('abc abc'):match('([^%s]+)%s%s')) -- https://github.com/Stepets/utf8.lua/issues/2
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("a+b") do res[#res + 1] = w end
+assert_equals({"ab","aab"}, res)
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("a-b") do res[#res + 1] = w end
+assert_equals({"ab","b","b","b","aab","b","b"}, res)
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("a*b") do res[#res + 1] = w end
+assert_equals({"ab","b","b","b","aab","b","b"}, res)
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("ba+") do res[#res + 1] = w end
+assert_equals({"ba","ba"}, res)
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("ba-") do res[#res + 1] = w end
+assert_equals({"b","b","b","b","b","b","b"}, res)
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("ba*") do res[#res + 1] = w end
+assert_equals({"b","ba","b","b","b","b","ba"}, res)
+
+assert_equals({"bacbbcaabbcba", "ba"}, {("aacabbacbbcaabbcbacaa"):match("((ba+).*%2)")})
+assert_equals({"bbacbbcaabbcb", "b"}, {("aacabbacbbcaabbcbacaa"):match("((ba*).*%2)")})
+
+res = {}
+for w in ("aacabbacbbcaabbcbacaa"):gmatch("((b+a*).-%2)") do res[#res + 1] = w end
+assert_equals({"bbacbb", "bb"}, res)
+
+assert_equals("a**", ("a**v"):match("a**+"))
+assert_equals("a", ("a**v"):match("a**-"))
+
+assert_equals({"test", "."}, {("test.lua"):match("(.-)([.])")})
+
+-- https://github.com/Stepets/utf8.lua/issues/3
+assert_equals({"ab", "c"}, {("abc"):match("^([ab]-)([^b]*)$")})
+assert_equals({"ab", ""}, {("ab"):match("^([ab]-)([^b]*)$")})
+assert_equals({"items.", ""}, {("items."):match("^(.-)([^.]*)$")})
+assert_equals({"", "items"}, {("items"):match("^(.-)([^.]*)$")})
+
+-- https://github.com/Stepets/utf8.lua/issues/4
+assert_equals({"ab.123", 1}, {("ab.?"):gsub("%?", "123")})
+
+-- https://github.com/Stepets/utf8.lua/issues/5
+assert_equals({"ab", 1}, {("ab"):gsub("a", "%0")})
+assert_equals({"ab", 1}, {("ab"):gsub("a", "%1")})
+
+assert_equals("c", ("abc"):match("c", -1))
+
+print("\ntests passed\n")
diff --git a/ar/.config/mpv/script-modules/utf8/test/test_compat.lua b/ar/.config/mpv/script-modules/utf8/test/test_compat.lua
new file mode 100644
index 0000000..d5042a5
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/test_compat.lua
@@ -0,0 +1,109 @@
+local utf8 = require 'init'
+utf8.config = {
+ debug = nil, --utf8:require("util").debug
+}
+utf8:init()
+print('testing utf8 library')
+
+local LUA_51, LUA_53 = false, false
+if "\xe4" == "xe4" then -- lua5.1
+ LUA_51 = true
+else -- luajit lua5.3
+ LUA_53 = true
+end
+
+assert(utf8.sub("123456789",2,4) == "234")
+assert(utf8.sub("123456789",7) == "789")
+assert(utf8.sub("123456789",7,6) == "")
+assert(utf8.sub("123456789",7,7) == "7")
+assert(utf8.sub("123456789",0,0) == "")
+assert(utf8.sub("123456789",-10,10) == "123456789")
+assert(utf8.sub("123456789",1,9) == "123456789")
+assert(utf8.sub("123456789",-10,-20) == "")
+assert(utf8.sub("123456789",-1) == "9")
+assert(utf8.sub("123456789",-4) == "6789")
+assert(utf8.sub("123456789",-6, -4) == "456")
+if not _no32 then
+ assert(utf8.sub("123456789",-2^31, -4) == "123456")
+ assert(utf8.sub("123456789",-2^31, 2^31 - 1) == "123456789")
+ assert(utf8.sub("123456789",-2^31, -2^31) == "")
+end
+assert(utf8.sub("\000123456789",3,5) == "234")
+assert(utf8.sub("\000123456789", 8) == "789")
+print('+')
+
+assert(utf8.find("123456789", "345") == 3)
+local a,b = utf8.find("123456789", "345")
+assert(utf8.sub("123456789", a, b) == "345")
+assert(utf8.find("1234567890123456789", "345", 3) == 3)
+assert(utf8.find("1234567890123456789", "345", 4) == 13)
+assert(utf8.find("1234567890123456789", "346", 4) == nil)
+assert(utf8.find("1234567890123456789", ".45", -9) == 13)
+assert(utf8.find("abcdefg", "\0", 5, 1) == nil)
+assert(utf8.find("", "") == 1)
+assert(utf8.find("", "", 1) == 1)
+assert(not utf8.find("", "", 2))
+assert(utf8.find('', 'aaa', 1) == nil)
+assert(('alo(.)alo'):find('(.)', 1, 1) == 4)
+print('+')
+
+assert(utf8.len("") == 0)
+assert(utf8.len("\0\0\0") == 3)
+assert(utf8.len("1234567890") == 10)
+
+assert(utf8.byte("a") == 97)
+if LUA_51 then
+ assert(utf8.byte("�") > 127)
+else
+ assert(utf8.byte("\xe4") > 127)
+end
+assert(utf8.byte(utf8.char(255)) == 255)
+assert(utf8.byte(utf8.char(0)) == 0)
+assert(utf8.byte("\0") == 0)
+assert(utf8.byte("\0\0alo\0x", -1) == string.byte('x'))
+assert(utf8.byte("ba", 2) == 97)
+assert(utf8.byte("\n\n", 2, -1) == 10)
+assert(utf8.byte("\n\n", 2, 2) == 10)
+assert(utf8.byte("") == nil)
+assert(utf8.byte("hi", -3) == nil)
+assert(utf8.byte("hi", 3) == nil)
+assert(utf8.byte("hi", 9, 10) == nil)
+assert(utf8.byte("hi", 2, 1) == nil)
+assert(utf8.char() == "")
+if LUA_53 then
+ assert(utf8.raw.char(0, 255, 0) == "\0\255\0") -- fails due 255 can't be utf8 byte
+ assert(utf8.char(0, 255, 0) == "\0\195\191\0")
+ assert(utf8.raw.char(0, utf8.byte("\xe4"), 0) == "\0\xe4\0")
+ assert(utf8.char(0, utf8.byte("\xe4"), 0) == "\0\195\164\0")
+ assert(utf8.raw.char(utf8.raw.byte("\xe4l\0�u", 1, -1)) == "\xe4l\0�u")
+ assert(utf8.raw.char(utf8.raw.byte("\xe4l\0�u", 1, -1)) == "\xe4l\0�u")
+ assert(utf8.raw.char(utf8.raw.byte("\xe4l\0�u", 1, 0)) == "")
+ assert(utf8.raw.char(utf8.raw.byte("\xe4l\0�u", -10, 100)) == "\xe4l\0�u")
+end
+
+assert(utf8.upper("ab\0c") == "AB\0C")
+assert(utf8.lower("\0ABCc%$") == "\0abcc%$")
+assert(utf8.rep('teste', 0) == '')
+assert(utf8.rep('t�s\00t�', 2) == 't�s\0t�t�s\000t�')
+assert(utf8.rep('', 10) == '')
+print('+')
+
+assert(utf8.upper("ab\0c") == "AB\0C")
+assert(utf8.lower("\0ABCc%$") == "\0abcc%$")
+
+assert(utf8.reverse"" == "")
+assert(utf8.reverse"\0\1\2\3" == "\3\2\1\0")
+assert(utf8.reverse"\0001234" == "4321\0")
+
+for i=0,30 do assert(utf8.len(string.rep('a', i)) == i) end
+
+print('+')
+
+do
+ local f = utf8.gmatch("1 2 3 4 5", "%d+")
+ assert(f() == "1")
+ local co = coroutine.wrap(f)
+ assert(co() == "2")
+end
+
+print('OK')
diff --git a/ar/.config/mpv/script-modules/utf8/test/test_pm.lua b/ar/.config/mpv/script-modules/utf8/test/test_pm.lua
new file mode 100644
index 0000000..9c8e472
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/test_pm.lua
@@ -0,0 +1,392 @@
+--[[--
+MIT License
+
+Copyright (c) 2018 Xavier Wang
+
+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.
+--]]--
+
+local utf8 = require 'init'
+utf8.config = {
+ debug = nil, --utf8:require("util").debug,
+}
+utf8:init()
+
+print('testing pattern matching')
+
+local
+function f(s, p)
+ local i,e = utf8.find(s, p)
+ if i then return utf8.sub(s, i, e) end
+end
+
+local
+function f1(s, p)
+ p = utf8.gsub(p, "%%([0-9])", function (s) return "%" .. (tonumber(s)+1) end)
+ p = utf8.gsub(p, "^(^?)", "%1()", 1)
+ p = utf8.gsub(p, "($?)$", "()%1", 1)
+ local t = {utf8.match(s, p)}
+ return utf8.sub(s, t[1], t[#t] - 1)
+end
+
+local
+a,b = utf8.find('', '') -- empty patterns are tricky
+assert(a == 1 and b == 0);
+a,b = utf8.find('alo', '')
+assert(a == 1 and b == 0)
+a,b = utf8.find('a\0o a\0o a\0o', 'a', 1) -- first position
+assert(a == 1 and b == 1)
+a,b = utf8.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the midle
+assert(a == 5 and b == 7)
+a,b = utf8.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the midle
+assert(a == 9 and b == 11)
+a,b = utf8.find('a\0a\0a\0a\0\0ab', '\0ab', 2); -- finds at the end
+assert(a == 9 and b == 11);
+a,b = utf8.find('a\0a\0a\0a\0\0ab', 'b') -- last position
+assert(a == 11 and b == 11)
+assert(utf8.find('a\0a\0a\0a\0\0ab', 'b\0') == nil) -- check ending
+assert(utf8.find('', '\0') == nil)
+assert(utf8.find('alo123alo', '12') == 4)
+assert(utf8.find('alo123alo', '^12') == nil)
+
+assert(utf8.match("aaab", ".*b") == "aaab")
+assert(utf8.match("aaa", ".*a") == "aaa")
+assert(utf8.match("b", ".*b") == "b")
+
+assert(utf8.match("aaab", ".+b") == "aaab")
+assert(utf8.match("aaa", ".+a") == "aaa")
+assert(not utf8.match("b", ".+b"))
+
+assert(utf8.match("aaab", ".?b") == "ab")
+assert(utf8.match("aaa", ".?a") == "aa")
+assert(utf8.match("b", ".?b") == "b")
+
+assert(f('aloALO', '%l*') == 'alo')
+assert(f('aLo_ALO', '%a*') == 'aLo')
+
+assert(f(" \n\r*&\n\r xuxu \n\n", "%g%g%g+") == "xuxu")
+
+assert(f('aaab', 'a*') == 'aaa');
+assert(f('aaa', '^.*$') == 'aaa');
+assert(f('aaa', 'b*') == '');
+assert(f('aaa', 'ab*a') == 'aa')
+assert(f('aba', 'ab*a') == 'aba')
+assert(f('aaab', 'a+') == 'aaa')
+assert(f('aaa', '^.+$') == 'aaa')
+assert(f('aaa', 'b+') == nil)
+assert(f('aaa', 'ab+a') == nil)
+assert(f('aba', 'ab+a') == 'aba')
+assert(f('a$a', '.$') == 'a')
+assert(f('a$a', '.%$') == 'a$')
+assert(f('a$a', '.$.') == 'a$a')
+assert(f('a$a', '$$') == nil)
+assert(f('a$b', 'a$') == nil)
+assert(f('a$a', '$') == '')
+assert(f('', 'b*') == '')
+assert(f('aaa', 'bb*') == nil)
+assert(f('aaab', 'a-') == '')
+assert(f('aaa', '^.-$') == 'aaa')
+assert(f('aabaaabaaabaaaba', 'b.*b') == 'baaabaaabaaab')
+assert(f('aabaaabaaabaaaba', 'b.-b') == 'baaab')
+assert(f('alo xo', '.o$') == 'xo')
+assert(f(' \n isto é assim', '%S%S*') == 'isto')
+assert(f(' \n isto é assim', '%S*$') == 'assim')
+assert(f(' \n isto é assim', '[a-z]*$') == 'assim')
+assert(f('um caracter ? extra', '[^%sa-z]') == '?')
+assert(f('', 'a?') == '')
+assert(f('á', 'á?') == 'á')
+assert(f('ábl', 'á?b?l?') == 'ábl')
+assert(f(' ábl', 'á?b?l?') == '')
+assert(f('aa', '^aa?a?a') == 'aa')
+assert(f(']]]áb', '[^]]') == 'á')
+assert(f("0alo alo", "%x*") == "0a")
+assert(f("alo alo", "%C+") == "alo alo")
+print('+')
+
+assert(f1('alo alx 123 b\0o b\0o', '(..*) %1') == "b\0o b\0o")
+assert(f1('axz123= 4= 4 34', '(.+)=(.*)=%2 %1') == '3= 4= 4 3')
+assert(f1('=======', '^(=*)=%1$') == '=======')
+assert(utf8.match('==========', '^([=]*)=%1$') == nil)
+
+local function range (i, j)
+ if i <= j then
+ return i, range(i+1, j)
+ end
+end
+
+local abc = utf8.char(range(0, 255));
+
+assert(utf8.len(abc) == 256)
+assert(string.len(abc) == 384)
+
+local
+function strset (p)
+ local res = {s=''}
+ utf8.gsub(abc, p, function (c) res.s = res.s .. c end)
+ return res.s
+end;
+
+local a, b, c, d, e, t
+
+-- local E = utf8.escape
+-- assert(utf8.len(strset(E'[%200-%210]')) == 11)
+
+assert(strset('[a-z]') == "abcdefghijklmnopqrstuvwxyz")
+assert(strset('[a-z%d]') == strset('[%da-uu-z]'))
+assert(strset('[a-]') == "-a")
+assert(strset('[^%W]') == strset('[%w]'))
+assert(strset('[]%%]') == '%]')
+assert(strset('[a%-z]') == '-az')
+assert(strset('[%^%[%-a%]%-b]') == '-[]^ab')
+-- assert(strset('%Z') == strset(E'[%1-%255]'))
+-- assert(strset('.') == strset(E'[%1-%255%%z]'))
+print('+');
+
+assert(utf8.match("alo xyzK", "(%w+)K") == "xyz")
+assert(utf8.match("254 K", "(%d*)K") == "")
+assert(utf8.match("alo ", "(%w*)$") == "")
+assert(utf8.match("alo ", "(%w+)$") == nil)
+assert(utf8.find("(álo)", "%(á") == 1)
+a, b, c, d, e = utf8.match("âlo alo", "^(((.).).* (%w*))$")
+assert(a == 'âlo alo' and b == 'âl' and c == 'â' and d == 'alo' and e == nil)
+a, b, c, d = utf8.match('0123456789', '(.+(.?)())')
+assert(a == '0123456789' and b == '' and c == 11 and d == nil)
+print('+')
+
+assert(utf8.gsub('ülo ülo', 'ü', 'x') == 'xlo xlo')
+assert(utf8.gsub('alo úlo ', ' +$', '') == 'alo úlo') -- trim
+assert(utf8.gsub(' alo alo ', '^%s*(.-)%s*$', '%1') == 'alo alo') -- double trim
+assert(utf8.gsub('alo alo \n 123\n ', '%s+', ' ') == 'alo alo 123 ')
+t = "abç d"
+a, b = utf8.gsub(t, '(.)', '%1@')
+assert('@'..a == utf8.gsub(t, '', '@') and b == 5)
+a, b = utf8.gsub('abçd', '(.)', '%0@', 2)
+assert(a == 'a@b@çd' and b == 2)
+assert(utf8.gsub('alo alo', '()[al]', '%1') == '12o 56o')
+assert(utf8.gsub("abc=xyz", "(%w*)(%p)(%w+)", "%3%2%1-%0") ==
+ "xyz=abc-abc=xyz")
+assert(utf8.gsub("abc", "%w", "%1%0") == "aabbcc")
+assert(utf8.gsub("abc", "%w+", "%0%1") == "abcabc")
+assert(utf8.gsub('áéí', '$', '\0óú') == 'áéí\0óú')
+assert(utf8.gsub('', '^', 'r') == 'r')
+assert(utf8.gsub('', '$', 'r') == 'r')
+print('+')
+
+assert(utf8.gsub("um (dois) tres (quatro)", "(%(%w+%))", utf8.upper) ==
+ "um (DOIS) tres (QUATRO)")
+
+do
+ local function setglobal (n,v) rawset(_G, n, v) end
+ utf8.gsub("a=roberto,roberto=a", "(%w+)=(%w%w*)", setglobal)
+ assert(_G.a=="roberto" and _G.roberto=="a")
+end
+
+function f(a,b) return utf8.gsub(a,'.',b) end
+assert(utf8.gsub("trocar tudo em |teste|b| é |beleza|al|", "|([^|]*)|([^|]*)|", f) ==
+ "trocar tudo em bbbbb é alalalalalal")
+
+local function dostring (s) return (loadstring or load)(s)() or "" end
+assert(utf8.gsub("alo $a=1$ novamente $return a$", "$([^$]*)%$", dostring) ==
+ "alo novamente 1")
+
+x = utf8.gsub("$local utf8=require'init' x=utf8.gsub('alo', '.', utf8.upper)$ assim vai para $return x$",
+ "$([^$]*)%$", dostring)
+assert(x == ' assim vai para ALO')
+
+local s,r
+t = {}
+s = 'a alo jose joao'
+r = utf8.gsub(s, '()(%w+)()', function (a,w,b)
+ assert(utf8.len(w) == b-a);
+ t[a] = b-a;
+ end)
+assert(s == r and t[1] == 1 and t[3] == 3 and t[7] == 4 and t[13] == 4)
+
+local
+function isbalanced (s)
+ return utf8.find(utf8.gsub(s, "%b()", ""), "[()]") == nil
+end
+
+assert(isbalanced("(9 ((8))(\0) 7) \0\0 a b ()(c)() a"))
+assert(not isbalanced("(9 ((8) 7) a b (\0 c) a"))
+assert(utf8.gsub("alo 'oi' alo", "%b''", '"') == 'alo " alo')
+
+
+local t = {"apple", "orange", "lime"; n=0}
+assert(utf8.gsub("x and x and x", "x", function () t.n=t.n+1; return t[t.n] end)
+ == "apple and orange and lime")
+
+t = {n=0}
+utf8.gsub("first second word", "%w%w*", function (w) t.n=t.n+1; t[t.n] = w end)
+assert(t[1] == "first" and t[2] == "second" and t[3] == "word" and t.n == 3)
+
+t = {n=0}
+assert(utf8.gsub("first second word", "%w+",
+ function (w) t.n=t.n+1; t[t.n] = w end, 2) == "first second word")
+assert(t[1] == "first" and t[2] == "second" and t[3] == nil)
+
+assert(not pcall(utf8.gsub, "alo", "(.", print))
+assert(not pcall(utf8.gsub, "alo", ".)", print))
+assert(not pcall(utf8.gsub, "alo", "(.", {}))
+assert(not pcall(utf8.gsub, "alo", "(.)", "%2"))
+assert(not pcall(utf8.gsub, "alo", "(%1)", "a"))
+--[[--
+Stepets: ignoring this test because it's probably bug in Lua.
+ %0 should be interpreted as capture reference only in replacement arg
+ it doesn't have sense in pattern
+--]]--
+-- assert(not pcall(utf8.gsub, "alo", "(%0)", "a"))
+
+-- bug since 2.5 (C-stack overflow)
+-- todo: benchmark OOM
+-- do
+-- local function f (size)
+-- local s = string.rep("a", size)
+-- local p = string.rep(".?", size)
+-- return pcall(utf8.match, s, p)
+-- end
+-- local r, m = f(80)
+-- assert(r and #m == 80)
+-- r, m = f(200000)
+-- assert(not r and utf8.find(m, "too complex"))
+-- end
+
+-- if not _soft then
+-- -- big strings
+-- local a = string.rep('a', 300000)
+-- assert(utf8.find(a, '^a*.?$'))
+-- assert(not utf8.find(a, '^a*.?b$'))
+-- assert(utf8.find(a, '^a-.?$'))
+
+-- -- bug in 5.1.2
+-- a = string.rep('a', 10000) .. string.rep('b', 10000)
+-- assert(not pcall(utf8.gsub, a, 'b'))
+-- end
+
+-- recursive nest of gsubs
+local function rev (s)
+ return utf8.gsub(s, "(.)(.+)", function (c,s1) return rev(s1)..c end)
+end
+
+local x = "abcdef"
+assert(rev(rev(x)) == x)
+
+
+-- gsub with tables
+assert(utf8.gsub("alo alo", ".", {}) == "alo alo")
+assert(utf8.gsub("alo alo", "(.)", {a="AA", l=""}) == "AAo AAo")
+assert(utf8.gsub("alo alo", "(.).", {a="AA", l="K"}) == "AAo AAo")
+assert(utf8.gsub("alo alo", "((.)(.?))", {al="AA", o=false}) == "AAo AAo")
+
+assert(utf8.gsub("alo alo", "().", {2,5,6}) == "256 alo")
+
+t = {}; setmetatable(t, {__index = function (t,s) return utf8.upper(s) end})
+assert(utf8.gsub("a alo b hi", "%w%w+", t) == "a ALO b HI")
+
+
+-- tests for gmatch
+local a = 0
+for i in utf8.gmatch('abcde', '()') do assert(i == a+1); a=i end
+assert(a==6)
+
+t = {n=0}
+for w in utf8.gmatch("first second word", "%w+") do
+ t.n=t.n+1; t[t.n] = w
+end
+assert(t[1] == "first" and t[2] == "second" and t[3] == "word")
+
+t = {3, 6, 9}
+for i in utf8.gmatch ("xuxx uu ppar r", "()(.)%2") do
+ assert(i == table.remove(t, 1))
+end
+assert(#t == 0)
+
+t = {}
+for i,j in utf8.gmatch("13 14 10 = 11, 15= 16, 22=23", "(%d+)%s*=%s*(%d+)") do
+ t[i] = j
+end
+a = 0
+for k,v in pairs(t) do assert(k+1 == v+0); a=a+1 end
+assert(a == 3)
+
+
+-- tests for `%f' (`frontiers')
+
+assert(utf8.gsub("aaa aa a aaa a", "%f[%w]a", "x") == "xaa xa x xaa x")
+assert(utf8.gsub("[[]] [][] [[[[", "%f[[].", "x") == "x[]] x]x] x[[[")
+assert(utf8.gsub("01abc45de3", "%f[%d]", ".") == ".01abc.45de.3")
+assert(utf8.gsub("01abc45 de3x", "%f[%D]%w", ".") == "01.bc45 de3.")
+-- local u = utf8.escape
+-- assert(utf8.gsub("function", u"%%f[%1-%255]%%w", ".") == ".unction")
+-- assert(utf8.gsub("function", u"%%f[^%1-%255]", ".") == "function.")
+
+--[[--
+Stepets: %z is Lua 5.1 class for representing \0
+ Lua 5.2, Lua 5.3 doesn't have it in documentation. So it's considered deprecated.
+--]]--
+assert(utf8.find("a", "%f[a]") == 1)
+assert(utf8.find("a", "%f[^%z]") == 1)
+assert(utf8.find("a", "%f[^%l]") == 2)
+assert(utf8.find("aba", "%f[a%z]") == 3)
+assert(utf8.find("aba", "%f[%z]") == 4)
+assert(not utf8.find("aba", "%f[%l%z]"))
+assert(not utf8.find("aba", "%f[^%l%z]"))
+
+local i, e = utf8.find(" alo aalo allo", "%f[%S].-%f[%s].-%f[%S]")
+assert(i == 2 and e == 5)
+local k = utf8.match(" alo aalo allo", "%f[%S](.-%f[%s].-%f[%S])")
+assert(k == 'alo ')
+
+local a = {1, 5, 9, 14, 17,}
+for k in utf8.gmatch("alo alo th02 is 1hat", "()%f[%w%d]") do
+ assert(table.remove(a, 1) == k)
+end
+assert(#a == 0)
+
+-- malformed patterns
+local function malform (p, m)
+ m = m or "malformed"
+ local r, msg = pcall(utf8.find, "a", p)
+ assert(not r and utf8.find(msg, m))
+end
+
+malform("[a")
+malform("[]")
+malform("[^]")
+malform("[a%]")
+malform("[a%")
+malform("%b", "unbalanced")
+malform("%ba", "unbalanced")
+malform("%")
+malform("%f", "missing")
+
+-- \0 in patterns
+assert(utf8.match("ab\0\1\2c", "[\0-\2]+") == "\0\1\2")
+assert(utf8.match("ab\0\1\2c", "[\0-\0]+") == "\0")
+assert(utf8.find("b$a", "$\0?") == 2)
+assert(utf8.find("abc\0efg", "%\0") == 4)
+assert(utf8.match("abc\0efg\0\1e\1g", "%b\0\1") == "\0efg\0\1e\1")
+assert(utf8.match("abc\0\0\0", "%\0+") == "\0\0\0")
+assert(utf8.match("abc\0\0\0", "%\0%\0?") == "\0\0")
+
+-- magic char after \0
+assert(utf8.find("abc\0\0","\0.") == 4)
+assert(utf8.find("abcx\0\0abc\0abc","x\0\0abc\0a.") == 4)
+
+print('OK')
diff --git a/ar/.config/mpv/script-modules/utf8/test/test_utf8data.lua b/ar/.config/mpv/script-modules/utf8/test/test_utf8data.lua
new file mode 100644
index 0000000..e915b2b
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/test_utf8data.lua
@@ -0,0 +1,15 @@
+local utf8uclc = require('init')
+utf8uclc.config = {
+ debug = nil,
+-- debug = utf8:require("util").debug,
+ conversion = {
+ uc_lc = setmetatable({}, {__index = function(self, idx) return "l" end}),
+ lc_uc = setmetatable({}, {__index = function(self, idx) return "u" end}),
+ }
+}
+utf8uclc:init()
+
+local assert_equals = require 'test.util'.assert_equals
+
+assert_equals(utf8uclc.lower("фыва"), "llll")
+assert_equals(utf8uclc.upper("фыва"), "uuuu")
diff --git a/ar/.config/mpv/script-modules/utf8/test/util.lua b/ar/.config/mpv/script-modules/utf8/test/util.lua
new file mode 100644
index 0000000..bdc25e5
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/test/util.lua
@@ -0,0 +1,75 @@
+require "test.strict"
+
+local function equals(t1, t2)
+ for k,v in pairs(t1) do
+ if t2[k] == nil then return false end
+ if type(t2[k]) == 'cdata' and type(v) == 'cdata' then
+ return true -- don't know how to compare
+ elseif type(t2[k]) == 'table' and type(v) == 'table' then
+ if not equals(t2[k], v) then return false end
+ else
+ if t2[k] ~= v then return false end
+ end
+ end
+ for k,v in pairs(t2) do
+ if t1[k] == nil then return false end
+ if type(t1[k]) == 'cdata' and type(v) == 'cdata' then
+ return true -- don't know how to compare
+ elseif type(t1[k]) == 'table' and type(v) == 'table' then
+ if not equals(t1[k], v) then return false end
+ else
+ if t1[k] ~= v then return false end
+ end
+ end
+ return true
+end
+
+local old_tostring = tostring
+local function tostring(v)
+ local type = type(v)
+ if type == 'table' then
+ local tbl = "{"
+ for k,v in pairs(v) do
+ tbl = tbl .. tostring(k) .. ' = ' .. tostring(v) .. ', '
+ end
+ return tbl .. '}'
+ else
+ return old_tostring(v)
+ end
+end
+
+local old_assert = assert
+local assert = function(cond, ...)
+ if not cond then
+ local data = {...}
+ local msg = ""
+ for _, v in pairs(data) do
+ local type = type(v)
+ if type == 'table' then
+ local tbl = "{"
+ for k,v in pairs(v) do
+ tbl = tbl .. tostring(k) .. ' = ' .. tostring(v) .. ', '
+ end
+ msg = msg .. tbl .. '}'
+ else
+ msg = msg .. tostring(v)
+ end
+ end
+ error(#data > 0 and msg or "assertion failed!")
+ end
+ return cond
+end
+
+local function assert_equals(a,b)
+ assert(
+ type(a) == 'table' and type(b) == 'table' and equals(a,b) or a == b,
+ "expected: ", a and a or tostring(a), "\n",
+ "got: ", b and b or tostring(b)
+ )
+end
+
+return {
+ equals = equals,
+ assert = assert,
+ assert_equals = assert_equals,
+}
diff --git a/ar/.config/mpv/script-modules/utf8/util.lua b/ar/.config/mpv/script-modules/utf8/util.lua
new file mode 100644
index 0000000..7723626
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8/util.lua
@@ -0,0 +1,64 @@
+return function(utf8)
+
+function utf8.util.copy(obj, deep)
+ if type(obj) == 'table' then
+ local result = {}
+ if deep then
+ for k,v in pairs(obj) do
+ result[k] = utf8.util.copy(v, true)
+ end
+ else
+ for k,v in pairs(obj) do
+ result[k] = v
+ end
+ end
+ return result
+ else
+ return obj
+ end
+end
+
+local function dump(val, tab)
+ tab = tab or ''
+
+ if type(val) == 'table' then
+ utf8.config.logger('{\n')
+ for k,v in pairs(val) do
+ utf8.config.logger(tab .. tostring(k) .. " = ")
+ dump(v, tab .. '\t')
+ utf8.config.logger("\n")
+ end
+ utf8.config.logger(tab .. '}\n')
+ else
+ utf8.config.logger(tostring(val))
+ end
+end
+
+function utf8.util.debug(...)
+ local t = {...}
+ for _, v in ipairs(t) do
+ if type(v) == "table" and not (getmetatable(v) or {}).__tostring then
+ dump(v, '\t')
+ else
+ utf8.config.logger(tostring(v), " ")
+ end
+ end
+
+ utf8.config.logger('\n')
+end
+
+function utf8.debug(...)
+ if utf8.config.debug then
+ utf8.config.debug(...)
+ end
+end
+
+function utf8.util.next(str, bs)
+ local nbs1 = utf8.next(str, bs)
+ local nbs2 = utf8.next(str, nbs1)
+ return utf8.raw.sub(str, nbs1, nbs2 - 1), nbs1
+end
+
+return utf8.util
+
+end
diff --git a/ar/.config/mpv/script-modules/utf8_data.lua b/ar/.config/mpv/script-modules/utf8_data.lua
new file mode 100644
index 0000000..bec6b9e
--- /dev/null
+++ b/ar/.config/mpv/script-modules/utf8_data.lua
@@ -0,0 +1,1865 @@
+utf8_lc_uc = {
+ ["a"] = "A",
+ ["b"] = "B",
+ ["c"] = "C",
+ ["d"] = "D",
+ ["e"] = "E",
+ ["f"] = "F",
+ ["g"] = "G",
+ ["h"] = "H",
+ ["i"] = "I",
+ ["j"] = "J",
+ ["k"] = "K",
+ ["l"] = "L",
+ ["m"] = "M",
+ ["n"] = "N",
+ ["o"] = "O",
+ ["p"] = "P",
+ ["q"] = "Q",
+ ["r"] = "R",
+ ["s"] = "S",
+ ["t"] = "T",
+ ["u"] = "U",
+ ["v"] = "V",
+ ["w"] = "W",
+ ["x"] = "X",
+ ["y"] = "Y",
+ ["z"] = "Z",
+ ["µ"] = "Μ",
+ ["à"] = "À",
+ ["á"] = "Á",
+ ["â"] = "Â",
+ ["ã"] = "Ã",
+ ["ä"] = "Ä",
+ ["å"] = "Å",
+ ["æ"] = "Æ",
+ ["ç"] = "Ç",
+ ["è"] = "È",
+ ["é"] = "É",
+ ["ê"] = "Ê",
+ ["ë"] = "Ë",
+ ["ì"] = "Ì",
+ ["í"] = "Í",
+ ["î"] = "Î",
+ ["ï"] = "Ï",
+ ["ð"] = "Ð",
+ ["ñ"] = "Ñ",
+ ["ò"] = "Ò",
+ ["ó"] = "Ó",
+ ["ô"] = "Ô",
+ ["õ"] = "Õ",
+ ["ö"] = "Ö",
+ ["ø"] = "Ø",
+ ["ù"] = "Ù",
+ ["ú"] = "Ú",
+ ["û"] = "Û",
+ ["ü"] = "Ü",
+ ["ý"] = "Ý",
+ ["þ"] = "Þ",
+ ["ÿ"] = "Ÿ",
+ ["ā"] = "Ā",
+ ["ă"] = "Ă",
+ ["ą"] = "Ą",
+ ["ć"] = "Ć",
+ ["ĉ"] = "Ĉ",
+ ["ċ"] = "Ċ",
+ ["č"] = "Č",
+ ["ď"] = "Ď",
+ ["đ"] = "Đ",
+ ["ē"] = "Ē",
+ ["ĕ"] = "Ĕ",
+ ["ė"] = "Ė",
+ ["ę"] = "Ę",
+ ["ě"] = "Ě",
+ ["ĝ"] = "Ĝ",
+ ["ğ"] = "Ğ",
+ ["ġ"] = "Ġ",
+ ["ģ"] = "Ģ",
+ ["ĥ"] = "Ĥ",
+ ["ħ"] = "Ħ",
+ ["ĩ"] = "Ĩ",
+ ["ī"] = "Ī",
+ ["ĭ"] = "Ĭ",
+ ["į"] = "Į",
+ ["ı"] = "I",
+ ["ij"] = "IJ",
+ ["ĵ"] = "Ĵ",
+ ["ķ"] = "Ķ",
+ ["ĺ"] = "Ĺ",
+ ["ļ"] = "Ļ",
+ ["ľ"] = "Ľ",
+ ["ŀ"] = "Ŀ",
+ ["ł"] = "Ł",
+ ["ń"] = "Ń",
+ ["ņ"] = "Ņ",
+ ["ň"] = "Ň",
+ ["ŋ"] = "Ŋ",
+ ["ō"] = "Ō",
+ ["ŏ"] = "Ŏ",
+ ["ő"] = "Ő",
+ ["œ"] = "Œ",
+ ["ŕ"] = "Ŕ",
+ ["ŗ"] = "Ŗ",
+ ["ř"] = "Ř",
+ ["ś"] = "Ś",
+ ["ŝ"] = "Ŝ",
+ ["ş"] = "Ş",
+ ["š"] = "Š",
+ ["ţ"] = "Ţ",
+ ["ť"] = "Ť",
+ ["ŧ"] = "Ŧ",
+ ["ũ"] = "Ũ",
+ ["ū"] = "Ū",
+ ["ŭ"] = "Ŭ",
+ ["ů"] = "Ů",
+ ["ű"] = "Ű",
+ ["ų"] = "Ų",
+ ["ŵ"] = "Ŵ",
+ ["ŷ"] = "Ŷ",
+ ["ź"] = "Ź",
+ ["ż"] = "Ż",
+ ["ž"] = "Ž",
+ ["ſ"] = "S",
+ ["ƀ"] = "Ƀ",
+ ["ƃ"] = "Ƃ",
+ ["ƅ"] = "Ƅ",
+ ["ƈ"] = "Ƈ",
+ ["ƌ"] = "Ƌ",
+ ["ƒ"] = "Ƒ",
+ ["ƕ"] = "Ƕ",
+ ["ƙ"] = "Ƙ",
+ ["ƚ"] = "Ƚ",
+ ["ƞ"] = "Ƞ",
+ ["ơ"] = "Ơ",
+ ["ƣ"] = "Ƣ",
+ ["ƥ"] = "Ƥ",
+ ["ƨ"] = "Ƨ",
+ ["ƭ"] = "Ƭ",
+ ["ư"] = "Ư",
+ ["ƴ"] = "Ƴ",
+ ["ƶ"] = "Ƶ",
+ ["ƹ"] = "Ƹ",
+ ["ƽ"] = "Ƽ",
+ ["ƿ"] = "Ƿ",
+ ["Dž"] = "DŽ",
+ ["dž"] = "DŽ",
+ ["Lj"] = "LJ",
+ ["lj"] = "LJ",
+ ["Nj"] = "NJ",
+ ["nj"] = "NJ",
+ ["ǎ"] = "Ǎ",
+ ["ǐ"] = "Ǐ",
+ ["ǒ"] = "Ǒ",
+ ["ǔ"] = "Ǔ",
+ ["ǖ"] = "Ǖ",
+ ["ǘ"] = "Ǘ",
+ ["ǚ"] = "Ǚ",
+ ["ǜ"] = "Ǜ",
+ ["ǝ"] = "Ǝ",
+ ["ǟ"] = "Ǟ",
+ ["ǡ"] = "Ǡ",
+ ["ǣ"] = "Ǣ",
+ ["ǥ"] = "Ǥ",
+ ["ǧ"] = "Ǧ",
+ ["ǩ"] = "Ǩ",
+ ["ǫ"] = "Ǫ",
+ ["ǭ"] = "Ǭ",
+ ["ǯ"] = "Ǯ",
+ ["Dz"] = "DZ",
+ ["dz"] = "DZ",
+ ["ǵ"] = "Ǵ",
+ ["ǹ"] = "Ǹ",
+ ["ǻ"] = "Ǻ",
+ ["ǽ"] = "Ǽ",
+ ["ǿ"] = "Ǿ",
+ ["ȁ"] = "Ȁ",
+ ["ȃ"] = "Ȃ",
+ ["ȅ"] = "Ȅ",
+ ["ȇ"] = "Ȇ",
+ ["ȉ"] = "Ȉ",
+ ["ȋ"] = "Ȋ",
+ ["ȍ"] = "Ȍ",
+ ["ȏ"] = "Ȏ",
+ ["ȑ"] = "Ȑ",
+ ["ȓ"] = "Ȓ",
+ ["ȕ"] = "Ȕ",
+ ["ȗ"] = "Ȗ",
+ ["ș"] = "Ș",
+ ["ț"] = "Ț",
+ ["ȝ"] = "Ȝ",
+ ["ȟ"] = "Ȟ",
+ ["ȣ"] = "Ȣ",
+ ["ȥ"] = "Ȥ",
+ ["ȧ"] = "Ȧ",
+ ["ȩ"] = "Ȩ",
+ ["ȫ"] = "Ȫ",
+ ["ȭ"] = "Ȭ",
+ ["ȯ"] = "Ȯ",
+ ["ȱ"] = "Ȱ",
+ ["ȳ"] = "Ȳ",
+ ["ȼ"] = "Ȼ",
+ ["ɂ"] = "Ɂ",
+ ["ɇ"] = "Ɇ",
+ ["ɉ"] = "Ɉ",
+ ["ɋ"] = "Ɋ",
+ ["ɍ"] = "Ɍ",
+ ["ɏ"] = "Ɏ",
+ ["ɓ"] = "Ɓ",
+ ["ɔ"] = "Ɔ",
+ ["ɖ"] = "Ɖ",
+ ["ɗ"] = "Ɗ",
+ ["ə"] = "Ə",
+ ["ɛ"] = "Ɛ",
+ ["ɠ"] = "Ɠ",
+ ["ɣ"] = "Ɣ",
+ ["ɨ"] = "Ɨ",
+ ["ɩ"] = "Ɩ",
+ ["ɫ"] = "Ɫ",
+ ["ɯ"] = "Ɯ",
+ ["ɲ"] = "Ɲ",
+ ["ɵ"] = "Ɵ",
+ ["ɽ"] = "Ɽ",
+ ["ʀ"] = "Ʀ",
+ ["ʃ"] = "Ʃ",
+ ["ʈ"] = "Ʈ",
+ ["ʉ"] = "Ʉ",
+ ["ʊ"] = "Ʊ",
+ ["ʋ"] = "Ʋ",
+ ["ʌ"] = "Ʌ",
+ ["ʒ"] = "Ʒ",
+ ["ͅ"] = "Ι",
+ ["ͻ"] = "Ͻ",
+ ["ͼ"] = "Ͼ",
+ ["ͽ"] = "Ͽ",
+ ["ά"] = "Ά",
+ ["έ"] = "Έ",
+ ["ή"] = "Ή",
+ ["ί"] = "Ί",
+ ["α"] = "Α",
+ ["β"] = "Β",
+ ["γ"] = "Γ",
+ ["δ"] = "Δ",
+ ["ε"] = "Ε",
+ ["ζ"] = "Ζ",
+ ["η"] = "Η",
+ ["θ"] = "Θ",
+ ["ι"] = "Ι",
+ ["κ"] = "Κ",
+ ["λ"] = "Λ",
+ ["μ"] = "Μ",
+ ["ν"] = "Ν",
+ ["ξ"] = "Ξ",
+ ["ο"] = "Ο",
+ ["π"] = "Π",
+ ["ρ"] = "Ρ",
+ ["ς"] = "Σ",
+ ["σ"] = "Σ",
+ ["τ"] = "Τ",
+ ["υ"] = "Υ",
+ ["φ"] = "Φ",
+ ["χ"] = "Χ",
+ ["ψ"] = "Ψ",
+ ["ω"] = "Ω",
+ ["ϊ"] = "Ϊ",
+ ["ϋ"] = "Ϋ",
+ ["ό"] = "Ό",
+ ["ύ"] = "Ύ",
+ ["ώ"] = "Ώ",
+ ["ϐ"] = "Β",
+ ["ϑ"] = "Θ",
+ ["ϕ"] = "Φ",
+ ["ϖ"] = "Π",
+ ["ϙ"] = "Ϙ",
+ ["ϛ"] = "Ϛ",
+ ["ϝ"] = "Ϝ",
+ ["ϟ"] = "Ϟ",
+ ["ϡ"] = "Ϡ",
+ ["ϣ"] = "Ϣ",
+ ["ϥ"] = "Ϥ",
+ ["ϧ"] = "Ϧ",
+ ["ϩ"] = "Ϩ",
+ ["ϫ"] = "Ϫ",
+ ["ϭ"] = "Ϭ",
+ ["ϯ"] = "Ϯ",
+ ["ϰ"] = "Κ",
+ ["ϱ"] = "Ρ",
+ ["ϲ"] = "Ϲ",
+ ["ϵ"] = "Ε",
+ ["ϸ"] = "Ϸ",
+ ["ϻ"] = "Ϻ",
+ ["а"] = "А",
+ ["б"] = "Б",
+ ["в"] = "В",
+ ["г"] = "Г",
+ ["д"] = "Д",
+ ["е"] = "Е",
+ ["ж"] = "Ж",
+ ["з"] = "З",
+ ["и"] = "И",
+ ["й"] = "Й",
+ ["к"] = "К",
+ ["л"] = "Л",
+ ["м"] = "М",
+ ["н"] = "Н",
+ ["о"] = "О",
+ ["п"] = "П",
+ ["р"] = "Р",
+ ["с"] = "С",
+ ["т"] = "Т",
+ ["у"] = "У",
+ ["ф"] = "Ф",
+ ["х"] = "Х",
+ ["ц"] = "Ц",
+ ["ч"] = "Ч",
+ ["ш"] = "Ш",
+ ["щ"] = "Щ",
+ ["ъ"] = "Ъ",
+ ["ы"] = "Ы",
+ ["ь"] = "Ь",
+ ["э"] = "Э",
+ ["ю"] = "Ю",
+ ["я"] = "Я",
+ ["ѐ"] = "Ѐ",
+ ["ё"] = "Ё",
+ ["ђ"] = "Ђ",
+ ["ѓ"] = "Ѓ",
+ ["є"] = "Є",
+ ["ѕ"] = "Ѕ",
+ ["і"] = "І",
+ ["ї"] = "Ї",
+ ["ј"] = "Ј",
+ ["љ"] = "Љ",
+ ["њ"] = "Њ",
+ ["ћ"] = "Ћ",
+ ["ќ"] = "Ќ",
+ ["ѝ"] = "Ѝ",
+ ["ў"] = "Ў",
+ ["џ"] = "Џ",
+ ["ѡ"] = "Ѡ",
+ ["ѣ"] = "Ѣ",
+ ["ѥ"] = "Ѥ",
+ ["ѧ"] = "Ѧ",
+ ["ѩ"] = "Ѩ",
+ ["ѫ"] = "Ѫ",
+ ["ѭ"] = "Ѭ",
+ ["ѯ"] = "Ѯ",
+ ["ѱ"] = "Ѱ",
+ ["ѳ"] = "Ѳ",
+ ["ѵ"] = "Ѵ",
+ ["ѷ"] = "Ѷ",
+ ["ѹ"] = "Ѹ",
+ ["ѻ"] = "Ѻ",
+ ["ѽ"] = "Ѽ",
+ ["ѿ"] = "Ѿ",
+ ["ҁ"] = "Ҁ",
+ ["ҋ"] = "Ҋ",
+ ["ҍ"] = "Ҍ",
+ ["ҏ"] = "Ҏ",
+ ["ґ"] = "Ґ",
+ ["ғ"] = "Ғ",
+ ["ҕ"] = "Ҕ",
+ ["җ"] = "Җ",
+ ["ҙ"] = "Ҙ",
+ ["қ"] = "Қ",
+ ["ҝ"] = "Ҝ",
+ ["ҟ"] = "Ҟ",
+ ["ҡ"] = "Ҡ",
+ ["ң"] = "Ң",
+ ["ҥ"] = "Ҥ",
+ ["ҧ"] = "Ҧ",
+ ["ҩ"] = "Ҩ",
+ ["ҫ"] = "Ҫ",
+ ["ҭ"] = "Ҭ",
+ ["ү"] = "Ү",
+ ["ұ"] = "Ұ",
+ ["ҳ"] = "Ҳ",
+ ["ҵ"] = "Ҵ",
+ ["ҷ"] = "Ҷ",
+ ["ҹ"] = "Ҹ",
+ ["һ"] = "Һ",
+ ["ҽ"] = "Ҽ",
+ ["ҿ"] = "Ҿ",
+ ["ӂ"] = "Ӂ",
+ ["ӄ"] = "Ӄ",
+ ["ӆ"] = "Ӆ",
+ ["ӈ"] = "Ӈ",
+ ["ӊ"] = "Ӊ",
+ ["ӌ"] = "Ӌ",
+ ["ӎ"] = "Ӎ",
+ ["ӏ"] = "Ӏ",
+ ["ӑ"] = "Ӑ",
+ ["ӓ"] = "Ӓ",
+ ["ӕ"] = "Ӕ",
+ ["ӗ"] = "Ӗ",
+ ["ә"] = "Ә",
+ ["ӛ"] = "Ӛ",
+ ["ӝ"] = "Ӝ",
+ ["ӟ"] = "Ӟ",
+ ["ӡ"] = "Ӡ",
+ ["ӣ"] = "Ӣ",
+ ["ӥ"] = "Ӥ",
+ ["ӧ"] = "Ӧ",
+ ["ө"] = "Ө",
+ ["ӫ"] = "Ӫ",
+ ["ӭ"] = "Ӭ",
+ ["ӯ"] = "Ӯ",
+ ["ӱ"] = "Ӱ",
+ ["ӳ"] = "Ӳ",
+ ["ӵ"] = "Ӵ",
+ ["ӷ"] = "Ӷ",
+ ["ӹ"] = "Ӹ",
+ ["ӻ"] = "Ӻ",
+ ["ӽ"] = "Ӽ",
+ ["ӿ"] = "Ӿ",
+ ["ԁ"] = "Ԁ",
+ ["ԃ"] = "Ԃ",
+ ["ԅ"] = "Ԅ",
+ ["ԇ"] = "Ԇ",
+ ["ԉ"] = "Ԉ",
+ ["ԋ"] = "Ԋ",
+ ["ԍ"] = "Ԍ",
+ ["ԏ"] = "Ԏ",
+ ["ԑ"] = "Ԑ",
+ ["ԓ"] = "Ԓ",
+ ["ա"] = "Ա",
+ ["բ"] = "Բ",
+ ["գ"] = "Գ",
+ ["դ"] = "Դ",
+ ["ե"] = "Ե",
+ ["զ"] = "Զ",
+ ["է"] = "Է",
+ ["ը"] = "Ը",
+ ["թ"] = "Թ",
+ ["ժ"] = "Ժ",
+ ["ի"] = "Ի",
+ ["լ"] = "Լ",
+ ["խ"] = "Խ",
+ ["ծ"] = "Ծ",
+ ["կ"] = "Կ",
+ ["հ"] = "Հ",
+ ["ձ"] = "Ձ",
+ ["ղ"] = "Ղ",
+ ["ճ"] = "Ճ",
+ ["մ"] = "Մ",
+ ["յ"] = "Յ",
+ ["ն"] = "Ն",
+ ["շ"] = "Շ",
+ ["ո"] = "Ո",
+ ["չ"] = "Չ",
+ ["պ"] = "Պ",
+ ["ջ"] = "Ջ",
+ ["ռ"] = "Ռ",
+ ["ս"] = "Ս",
+ ["վ"] = "Վ",
+ ["տ"] = "Տ",
+ ["ր"] = "Ր",
+ ["ց"] = "Ց",
+ ["ւ"] = "Ւ",
+ ["փ"] = "Փ",
+ ["ք"] = "Ք",
+ ["օ"] = "Օ",
+ ["ֆ"] = "Ֆ",
+ ["ᵽ"] = "Ᵽ",
+ ["ḁ"] = "Ḁ",
+ ["ḃ"] = "Ḃ",
+ ["ḅ"] = "Ḅ",
+ ["ḇ"] = "Ḇ",
+ ["ḉ"] = "Ḉ",
+ ["ḋ"] = "Ḋ",
+ ["ḍ"] = "Ḍ",
+ ["ḏ"] = "Ḏ",
+ ["ḑ"] = "Ḑ",
+ ["ḓ"] = "Ḓ",
+ ["ḕ"] = "Ḕ",
+ ["ḗ"] = "Ḗ",
+ ["ḙ"] = "Ḙ",
+ ["ḛ"] = "Ḛ",
+ ["ḝ"] = "Ḝ",
+ ["ḟ"] = "Ḟ",
+ ["ḡ"] = "Ḡ",
+ ["ḣ"] = "Ḣ",
+ ["ḥ"] = "Ḥ",
+ ["ḧ"] = "Ḧ",
+ ["ḩ"] = "Ḩ",
+ ["ḫ"] = "Ḫ",
+ ["ḭ"] = "Ḭ",
+ ["ḯ"] = "Ḯ",
+ ["ḱ"] = "Ḱ",
+ ["ḳ"] = "Ḳ",
+ ["ḵ"] = "Ḵ",
+ ["ḷ"] = "Ḷ",
+ ["ḹ"] = "Ḹ",
+ ["ḻ"] = "Ḻ",
+ ["ḽ"] = "Ḽ",
+ ["ḿ"] = "Ḿ",
+ ["ṁ"] = "Ṁ",
+ ["ṃ"] = "Ṃ",
+ ["ṅ"] = "Ṅ",
+ ["ṇ"] = "Ṇ",
+ ["ṉ"] = "Ṉ",
+ ["ṋ"] = "Ṋ",
+ ["ṍ"] = "Ṍ",
+ ["ṏ"] = "Ṏ",
+ ["ṑ"] = "Ṑ",
+ ["ṓ"] = "Ṓ",
+ ["ṕ"] = "Ṕ",
+ ["ṗ"] = "Ṗ",
+ ["ṙ"] = "Ṙ",
+ ["ṛ"] = "Ṛ",
+ ["ṝ"] = "Ṝ",
+ ["ṟ"] = "Ṟ",
+ ["ṡ"] = "Ṡ",
+ ["ṣ"] = "Ṣ",
+ ["ṥ"] = "Ṥ",
+ ["ṧ"] = "Ṧ",
+ ["ṩ"] = "Ṩ",
+ ["ṫ"] = "Ṫ",
+ ["ṭ"] = "Ṭ",
+ ["ṯ"] = "Ṯ",
+ ["ṱ"] = "Ṱ",
+ ["ṳ"] = "Ṳ",
+ ["ṵ"] = "Ṵ",
+ ["ṷ"] = "Ṷ",
+ ["ṹ"] = "Ṹ",
+ ["ṻ"] = "Ṻ",
+ ["ṽ"] = "Ṽ",
+ ["ṿ"] = "Ṿ",
+ ["ẁ"] = "Ẁ",
+ ["ẃ"] = "Ẃ",
+ ["ẅ"] = "Ẅ",
+ ["ẇ"] = "Ẇ",
+ ["ẉ"] = "Ẉ",
+ ["ẋ"] = "Ẋ",
+ ["ẍ"] = "Ẍ",
+ ["ẏ"] = "Ẏ",
+ ["ẑ"] = "Ẑ",
+ ["ẓ"] = "Ẓ",
+ ["ẕ"] = "Ẕ",
+ ["ẛ"] = "Ṡ",
+ ["ạ"] = "Ạ",
+ ["ả"] = "Ả",
+ ["ấ"] = "Ấ",
+ ["ầ"] = "Ầ",
+ ["ẩ"] = "Ẩ",
+ ["ẫ"] = "Ẫ",
+ ["ậ"] = "Ậ",
+ ["ắ"] = "Ắ",
+ ["ằ"] = "Ằ",
+ ["ẳ"] = "Ẳ",
+ ["ẵ"] = "Ẵ",
+ ["ặ"] = "Ặ",
+ ["ẹ"] = "Ẹ",
+ ["ẻ"] = "Ẻ",
+ ["ẽ"] = "Ẽ",
+ ["ế"] = "Ế",
+ ["ề"] = "Ề",
+ ["ể"] = "Ể",
+ ["ễ"] = "Ễ",
+ ["ệ"] = "Ệ",
+ ["ỉ"] = "Ỉ",
+ ["ị"] = "Ị",
+ ["ọ"] = "Ọ",
+ ["ỏ"] = "Ỏ",
+ ["ố"] = "Ố",
+ ["ồ"] = "Ồ",
+ ["ổ"] = "Ổ",
+ ["ỗ"] = "Ỗ",
+ ["ộ"] = "Ộ",
+ ["ớ"] = "Ớ",
+ ["ờ"] = "Ờ",
+ ["ở"] = "Ở",
+ ["ỡ"] = "Ỡ",
+ ["ợ"] = "Ợ",
+ ["ụ"] = "Ụ",
+ ["ủ"] = "Ủ",
+ ["ứ"] = "Ứ",
+ ["ừ"] = "Ừ",
+ ["ử"] = "Ử",
+ ["ữ"] = "Ữ",
+ ["ự"] = "Ự",
+ ["ỳ"] = "Ỳ",
+ ["ỵ"] = "Ỵ",
+ ["ỷ"] = "Ỷ",
+ ["ỹ"] = "Ỹ",
+ ["ἀ"] = "Ἀ",
+ ["ἁ"] = "Ἁ",
+ ["ἂ"] = "Ἂ",
+ ["ἃ"] = "Ἃ",
+ ["ἄ"] = "Ἄ",
+ ["ἅ"] = "Ἅ",
+ ["ἆ"] = "Ἆ",
+ ["ἇ"] = "Ἇ",
+ ["ἐ"] = "Ἐ",
+ ["ἑ"] = "Ἑ",
+ ["ἒ"] = "Ἒ",
+ ["ἓ"] = "Ἓ",
+ ["ἔ"] = "Ἔ",
+ ["ἕ"] = "Ἕ",
+ ["ἠ"] = "Ἠ",
+ ["ἡ"] = "Ἡ",
+ ["ἢ"] = "Ἢ",
+ ["ἣ"] = "Ἣ",
+ ["ἤ"] = "Ἤ",
+ ["ἥ"] = "Ἥ",
+ ["ἦ"] = "Ἦ",
+ ["ἧ"] = "Ἧ",
+ ["ἰ"] = "Ἰ",
+ ["ἱ"] = "Ἱ",
+ ["ἲ"] = "Ἲ",
+ ["ἳ"] = "Ἳ",
+ ["ἴ"] = "Ἴ",
+ ["ἵ"] = "Ἵ",
+ ["ἶ"] = "Ἶ",
+ ["ἷ"] = "Ἷ",
+ ["ὀ"] = "Ὀ",
+ ["ὁ"] = "Ὁ",
+ ["ὂ"] = "Ὂ",
+ ["ὃ"] = "Ὃ",
+ ["ὄ"] = "Ὄ",
+ ["ὅ"] = "Ὅ",
+ ["ὑ"] = "Ὑ",
+ ["ὓ"] = "Ὓ",
+ ["ὕ"] = "Ὕ",
+ ["ὗ"] = "Ὗ",
+ ["ὠ"] = "Ὠ",
+ ["ὡ"] = "Ὡ",
+ ["ὢ"] = "Ὢ",
+ ["ὣ"] = "Ὣ",
+ ["ὤ"] = "Ὤ",
+ ["ὥ"] = "Ὥ",
+ ["ὦ"] = "Ὦ",
+ ["ὧ"] = "Ὧ",
+ ["ὰ"] = "Ὰ",
+ ["ά"] = "Ά",
+ ["ὲ"] = "Ὲ",
+ ["έ"] = "Έ",
+ ["ὴ"] = "Ὴ",
+ ["ή"] = "Ή",
+ ["ὶ"] = "Ὶ",
+ ["ί"] = "Ί",
+ ["ὸ"] = "Ὸ",
+ ["ό"] = "Ό",
+ ["ὺ"] = "Ὺ",
+ ["ύ"] = "Ύ",
+ ["ὼ"] = "Ὼ",
+ ["ώ"] = "Ώ",
+ ["ᾀ"] = "ᾈ",
+ ["ᾁ"] = "ᾉ",
+ ["ᾂ"] = "ᾊ",
+ ["ᾃ"] = "ᾋ",
+ ["ᾄ"] = "ᾌ",
+ ["ᾅ"] = "ᾍ",
+ ["ᾆ"] = "ᾎ",
+ ["ᾇ"] = "ᾏ",
+ ["ᾐ"] = "ᾘ",
+ ["ᾑ"] = "ᾙ",
+ ["ᾒ"] = "ᾚ",
+ ["ᾓ"] = "ᾛ",
+ ["ᾔ"] = "ᾜ",
+ ["ᾕ"] = "ᾝ",
+ ["ᾖ"] = "ᾞ",
+ ["ᾗ"] = "ᾟ",
+ ["ᾠ"] = "ᾨ",
+ ["ᾡ"] = "ᾩ",
+ ["ᾢ"] = "ᾪ",
+ ["ᾣ"] = "ᾫ",
+ ["ᾤ"] = "ᾬ",
+ ["ᾥ"] = "ᾭ",
+ ["ᾦ"] = "ᾮ",
+ ["ᾧ"] = "ᾯ",
+ ["ᾰ"] = "Ᾰ",
+ ["ᾱ"] = "Ᾱ",
+ ["ᾳ"] = "ᾼ",
+ ["ι"] = "Ι",
+ ["ῃ"] = "ῌ",
+ ["ῐ"] = "Ῐ",
+ ["ῑ"] = "Ῑ",
+ ["ῠ"] = "Ῠ",
+ ["ῡ"] = "Ῡ",
+ ["ῥ"] = "Ῥ",
+ ["ῳ"] = "ῼ",
+ ["ⅎ"] = "Ⅎ",
+ ["ⅰ"] = "Ⅰ",
+ ["ⅱ"] = "Ⅱ",
+ ["ⅲ"] = "Ⅲ",
+ ["ⅳ"] = "Ⅳ",
+ ["ⅴ"] = "Ⅴ",
+ ["ⅵ"] = "Ⅵ",
+ ["ⅶ"] = "Ⅶ",
+ ["ⅷ"] = "Ⅷ",
+ ["ⅸ"] = "Ⅸ",
+ ["ⅹ"] = "Ⅹ",
+ ["ⅺ"] = "Ⅺ",
+ ["ⅻ"] = "Ⅻ",
+ ["ⅼ"] = "Ⅼ",
+ ["ⅽ"] = "Ⅽ",
+ ["ⅾ"] = "Ⅾ",
+ ["ⅿ"] = "Ⅿ",
+ ["ↄ"] = "Ↄ",
+ ["ⓐ"] = "Ⓐ",
+ ["ⓑ"] = "Ⓑ",
+ ["ⓒ"] = "Ⓒ",
+ ["ⓓ"] = "Ⓓ",
+ ["ⓔ"] = "Ⓔ",
+ ["ⓕ"] = "Ⓕ",
+ ["ⓖ"] = "Ⓖ",
+ ["ⓗ"] = "Ⓗ",
+ ["ⓘ"] = "Ⓘ",
+ ["ⓙ"] = "Ⓙ",
+ ["ⓚ"] = "Ⓚ",
+ ["ⓛ"] = "Ⓛ",
+ ["ⓜ"] = "Ⓜ",
+ ["ⓝ"] = "Ⓝ",
+ ["ⓞ"] = "Ⓞ",
+ ["ⓟ"] = "Ⓟ",
+ ["ⓠ"] = "Ⓠ",
+ ["ⓡ"] = "Ⓡ",
+ ["ⓢ"] = "Ⓢ",
+ ["ⓣ"] = "Ⓣ",
+ ["ⓤ"] = "Ⓤ",
+ ["ⓥ"] = "Ⓥ",
+ ["ⓦ"] = "Ⓦ",
+ ["ⓧ"] = "Ⓧ",
+ ["ⓨ"] = "Ⓨ",
+ ["ⓩ"] = "Ⓩ",
+ ["ⰰ"] = "Ⰰ",
+ ["ⰱ"] = "Ⰱ",
+ ["ⰲ"] = "Ⰲ",
+ ["ⰳ"] = "Ⰳ",
+ ["ⰴ"] = "Ⰴ",
+ ["ⰵ"] = "Ⰵ",
+ ["ⰶ"] = "Ⰶ",
+ ["ⰷ"] = "Ⰷ",
+ ["ⰸ"] = "Ⰸ",
+ ["ⰹ"] = "Ⰹ",
+ ["ⰺ"] = "Ⰺ",
+ ["ⰻ"] = "Ⰻ",
+ ["ⰼ"] = "Ⰼ",
+ ["ⰽ"] = "Ⰽ",
+ ["ⰾ"] = "Ⰾ",
+ ["ⰿ"] = "Ⰿ",
+ ["ⱀ"] = "Ⱀ",
+ ["ⱁ"] = "Ⱁ",
+ ["ⱂ"] = "Ⱂ",
+ ["ⱃ"] = "Ⱃ",
+ ["ⱄ"] = "Ⱄ",
+ ["ⱅ"] = "Ⱅ",
+ ["ⱆ"] = "Ⱆ",
+ ["ⱇ"] = "Ⱇ",
+ ["ⱈ"] = "Ⱈ",
+ ["ⱉ"] = "Ⱉ",
+ ["ⱊ"] = "Ⱊ",
+ ["ⱋ"] = "Ⱋ",
+ ["ⱌ"] = "Ⱌ",
+ ["ⱍ"] = "Ⱍ",
+ ["ⱎ"] = "Ⱎ",
+ ["ⱏ"] = "Ⱏ",
+ ["ⱐ"] = "Ⱐ",
+ ["ⱑ"] = "Ⱑ",
+ ["ⱒ"] = "Ⱒ",
+ ["ⱓ"] = "Ⱓ",
+ ["ⱔ"] = "Ⱔ",
+ ["ⱕ"] = "Ⱕ",
+ ["ⱖ"] = "Ⱖ",
+ ["ⱗ"] = "Ⱗ",
+ ["ⱘ"] = "Ⱘ",
+ ["ⱙ"] = "Ⱙ",
+ ["ⱚ"] = "Ⱚ",
+ ["ⱛ"] = "Ⱛ",
+ ["ⱜ"] = "Ⱜ",
+ ["ⱝ"] = "Ⱝ",
+ ["ⱞ"] = "Ⱞ",
+ ["ⱡ"] = "Ⱡ",
+ ["ⱥ"] = "Ⱥ",
+ ["ⱦ"] = "Ⱦ",
+ ["ⱨ"] = "Ⱨ",
+ ["ⱪ"] = "Ⱪ",
+ ["ⱬ"] = "Ⱬ",
+ ["ⱶ"] = "Ⱶ",
+ ["ⲁ"] = "Ⲁ",
+ ["ⲃ"] = "Ⲃ",
+ ["ⲅ"] = "Ⲅ",
+ ["ⲇ"] = "Ⲇ",
+ ["ⲉ"] = "Ⲉ",
+ ["ⲋ"] = "Ⲋ",
+ ["ⲍ"] = "Ⲍ",
+ ["ⲏ"] = "Ⲏ",
+ ["ⲑ"] = "Ⲑ",
+ ["ⲓ"] = "Ⲓ",
+ ["ⲕ"] = "Ⲕ",
+ ["ⲗ"] = "Ⲗ",
+ ["ⲙ"] = "Ⲙ",
+ ["ⲛ"] = "Ⲛ",
+ ["ⲝ"] = "Ⲝ",
+ ["ⲟ"] = "Ⲟ",
+ ["ⲡ"] = "Ⲡ",
+ ["ⲣ"] = "Ⲣ",
+ ["ⲥ"] = "Ⲥ",
+ ["ⲧ"] = "Ⲧ",
+ ["ⲩ"] = "Ⲩ",
+ ["ⲫ"] = "Ⲫ",
+ ["ⲭ"] = "Ⲭ",
+ ["ⲯ"] = "Ⲯ",
+ ["ⲱ"] = "Ⲱ",
+ ["ⲳ"] = "Ⲳ",
+ ["ⲵ"] = "Ⲵ",
+ ["ⲷ"] = "Ⲷ",
+ ["ⲹ"] = "Ⲹ",
+ ["ⲻ"] = "Ⲻ",
+ ["ⲽ"] = "Ⲽ",
+ ["ⲿ"] = "Ⲿ",
+ ["ⳁ"] = "Ⳁ",
+ ["ⳃ"] = "Ⳃ",
+ ["ⳅ"] = "Ⳅ",
+ ["ⳇ"] = "Ⳇ",
+ ["ⳉ"] = "Ⳉ",
+ ["ⳋ"] = "Ⳋ",
+ ["ⳍ"] = "Ⳍ",
+ ["ⳏ"] = "Ⳏ",
+ ["ⳑ"] = "Ⳑ",
+ ["ⳓ"] = "Ⳓ",
+ ["ⳕ"] = "Ⳕ",
+ ["ⳗ"] = "Ⳗ",
+ ["ⳙ"] = "Ⳙ",
+ ["ⳛ"] = "Ⳛ",
+ ["ⳝ"] = "Ⳝ",
+ ["ⳟ"] = "Ⳟ",
+ ["ⳡ"] = "Ⳡ",
+ ["ⳣ"] = "Ⳣ",
+ ["ⴀ"] = "Ⴀ",
+ ["ⴁ"] = "Ⴁ",
+ ["ⴂ"] = "Ⴂ",
+ ["ⴃ"] = "Ⴃ",
+ ["ⴄ"] = "Ⴄ",
+ ["ⴅ"] = "Ⴅ",
+ ["ⴆ"] = "Ⴆ",
+ ["ⴇ"] = "Ⴇ",
+ ["ⴈ"] = "Ⴈ",
+ ["ⴉ"] = "Ⴉ",
+ ["ⴊ"] = "Ⴊ",
+ ["ⴋ"] = "Ⴋ",
+ ["ⴌ"] = "Ⴌ",
+ ["ⴍ"] = "Ⴍ",
+ ["ⴎ"] = "Ⴎ",
+ ["ⴏ"] = "Ⴏ",
+ ["ⴐ"] = "Ⴐ",
+ ["ⴑ"] = "Ⴑ",
+ ["ⴒ"] = "Ⴒ",
+ ["ⴓ"] = "Ⴓ",
+ ["ⴔ"] = "Ⴔ",
+ ["ⴕ"] = "Ⴕ",
+ ["ⴖ"] = "Ⴖ",
+ ["ⴗ"] = "Ⴗ",
+ ["ⴘ"] = "Ⴘ",
+ ["ⴙ"] = "Ⴙ",
+ ["ⴚ"] = "Ⴚ",
+ ["ⴛ"] = "Ⴛ",
+ ["ⴜ"] = "Ⴜ",
+ ["ⴝ"] = "Ⴝ",
+ ["ⴞ"] = "Ⴞ",
+ ["ⴟ"] = "Ⴟ",
+ ["ⴠ"] = "Ⴠ",
+ ["ⴡ"] = "Ⴡ",
+ ["ⴢ"] = "Ⴢ",
+ ["ⴣ"] = "Ⴣ",
+ ["ⴤ"] = "Ⴤ",
+ ["ⴥ"] = "Ⴥ",
+ ["a"] = "A",
+ ["b"] = "B",
+ ["c"] = "C",
+ ["d"] = "D",
+ ["e"] = "E",
+ ["f"] = "F",
+ ["g"] = "G",
+ ["h"] = "H",
+ ["i"] = "I",
+ ["j"] = "J",
+ ["k"] = "K",
+ ["l"] = "L",
+ ["m"] = "M",
+ ["n"] = "N",
+ ["o"] = "O",
+ ["p"] = "P",
+ ["q"] = "Q",
+ ["r"] = "R",
+ ["s"] = "S",
+ ["t"] = "T",
+ ["u"] = "U",
+ ["v"] = "V",
+ ["w"] = "W",
+ ["x"] = "X",
+ ["y"] = "Y",
+ ["z"] = "Z",
+ ["𐐨"] = "𐐀",
+ ["𐐩"] = "𐐁",
+ ["𐐪"] = "𐐂",
+ ["𐐫"] = "𐐃",
+ ["𐐬"] = "𐐄",
+ ["𐐭"] = "𐐅",
+ ["𐐮"] = "𐐆",
+ ["𐐯"] = "𐐇",
+ ["𐐰"] = "𐐈",
+ ["𐐱"] = "𐐉",
+ ["𐐲"] = "𐐊",
+ ["𐐳"] = "𐐋",
+ ["𐐴"] = "𐐌",
+ ["𐐵"] = "𐐍",
+ ["𐐶"] = "𐐎",
+ ["𐐷"] = "𐐏",
+ ["𐐸"] = "𐐐",
+ ["𐐹"] = "𐐑",
+ ["𐐺"] = "𐐒",
+ ["𐐻"] = "𐐓",
+ ["𐐼"] = "𐐔",
+ ["𐐽"] = "𐐕",
+ ["𐐾"] = "𐐖",
+ ["𐐿"] = "𐐗",
+ ["𐑀"] = "𐐘",
+ ["𐑁"] = "𐐙",
+ ["𐑂"] = "𐐚",
+ ["𐑃"] = "𐐛",
+ ["𐑄"] = "𐐜",
+ ["𐑅"] = "𐐝",
+ ["𐑆"] = "𐐞",
+ ["𐑇"] = "𐐟",
+ ["𐑈"] = "𐐠",
+ ["𐑉"] = "𐐡",
+ ["𐑊"] = "𐐢",
+ ["𐑋"] = "𐐣",
+ ["𐑌"] = "𐐤",
+ ["𐑍"] = "𐐥",
+ ["𐑎"] = "𐐦",
+ ["𐑏"] = "𐐧",
+}
+
+
+utf8_uc_lc = {
+ ["A"] = "a",
+ ["B"] = "b",
+ ["C"] = "c",
+ ["D"] = "d",
+ ["E"] = "e",
+ ["F"] = "f",
+ ["G"] = "g",
+ ["H"] = "h",
+ ["I"] = "i",
+ ["J"] = "j",
+ ["K"] = "k",
+ ["L"] = "l",
+ ["M"] = "m",
+ ["N"] = "n",
+ ["O"] = "o",
+ ["P"] = "p",
+ ["Q"] = "q",
+ ["R"] = "r",
+ ["S"] = "s",
+ ["T"] = "t",
+ ["U"] = "u",
+ ["V"] = "v",
+ ["W"] = "w",
+ ["X"] = "x",
+ ["Y"] = "y",
+ ["Z"] = "z",
+ ["À"] = "à",
+ ["Á"] = "á",
+ ["Â"] = "â",
+ ["Ã"] = "ã",
+ ["Ä"] = "ä",
+ ["Å"] = "å",
+ ["Æ"] = "æ",
+ ["Ç"] = "ç",
+ ["È"] = "è",
+ ["É"] = "é",
+ ["Ê"] = "ê",
+ ["Ë"] = "ë",
+ ["Ì"] = "ì",
+ ["Í"] = "í",
+ ["Î"] = "î",
+ ["Ï"] = "ï",
+ ["Ð"] = "ð",
+ ["Ñ"] = "ñ",
+ ["Ò"] = "ò",
+ ["Ó"] = "ó",
+ ["Ô"] = "ô",
+ ["Õ"] = "õ",
+ ["Ö"] = "ö",
+ ["Ø"] = "ø",
+ ["Ù"] = "ù",
+ ["Ú"] = "ú",
+ ["Û"] = "û",
+ ["Ü"] = "ü",
+ ["Ý"] = "ý",
+ ["Þ"] = "þ",
+ ["Ā"] = "ā",
+ ["Ă"] = "ă",
+ ["Ą"] = "ą",
+ ["Ć"] = "ć",
+ ["Ĉ"] = "ĉ",
+ ["Ċ"] = "ċ",
+ ["Č"] = "č",
+ ["Ď"] = "ď",
+ ["Đ"] = "đ",
+ ["Ē"] = "ē",
+ ["Ĕ"] = "ĕ",
+ ["Ė"] = "ė",
+ ["Ę"] = "ę",
+ ["Ě"] = "ě",
+ ["Ĝ"] = "ĝ",
+ ["Ğ"] = "ğ",
+ ["Ġ"] = "ġ",
+ ["Ģ"] = "ģ",
+ ["Ĥ"] = "ĥ",
+ ["Ħ"] = "ħ",
+ ["Ĩ"] = "ĩ",
+ ["Ī"] = "ī",
+ ["Ĭ"] = "ĭ",
+ ["Į"] = "į",
+ ["İ"] = "i",
+ ["IJ"] = "ij",
+ ["Ĵ"] = "ĵ",
+ ["Ķ"] = "ķ",
+ ["Ĺ"] = "ĺ",
+ ["Ļ"] = "ļ",
+ ["Ľ"] = "ľ",
+ ["Ŀ"] = "ŀ",
+ ["Ł"] = "ł",
+ ["Ń"] = "ń",
+ ["Ņ"] = "ņ",
+ ["Ň"] = "ň",
+ ["Ŋ"] = "ŋ",
+ ["Ō"] = "ō",
+ ["Ŏ"] = "ŏ",
+ ["Ő"] = "ő",
+ ["Œ"] = "œ",
+ ["Ŕ"] = "ŕ",
+ ["Ŗ"] = "ŗ",
+ ["Ř"] = "ř",
+ ["Ś"] = "ś",
+ ["Ŝ"] = "ŝ",
+ ["Ş"] = "ş",
+ ["Š"] = "š",
+ ["Ţ"] = "ţ",
+ ["Ť"] = "ť",
+ ["Ŧ"] = "ŧ",
+ ["Ũ"] = "ũ",
+ ["Ū"] = "ū",
+ ["Ŭ"] = "ŭ",
+ ["Ů"] = "ů",
+ ["Ű"] = "ű",
+ ["Ų"] = "ų",
+ ["Ŵ"] = "ŵ",
+ ["Ŷ"] = "ŷ",
+ ["Ÿ"] = "ÿ",
+ ["Ź"] = "ź",
+ ["Ż"] = "ż",
+ ["Ž"] = "ž",
+ ["Ɓ"] = "ɓ",
+ ["Ƃ"] = "ƃ",
+ ["Ƅ"] = "ƅ",
+ ["Ɔ"] = "ɔ",
+ ["Ƈ"] = "ƈ",
+ ["Ɖ"] = "ɖ",
+ ["Ɗ"] = "ɗ",
+ ["Ƌ"] = "ƌ",
+ ["Ǝ"] = "ǝ",
+ ["Ə"] = "ə",
+ ["Ɛ"] = "ɛ",
+ ["Ƒ"] = "ƒ",
+ ["Ɠ"] = "ɠ",
+ ["Ɣ"] = "ɣ",
+ ["Ɩ"] = "ɩ",
+ ["Ɨ"] = "ɨ",
+ ["Ƙ"] = "ƙ",
+ ["Ɯ"] = "ɯ",
+ ["Ɲ"] = "ɲ",
+ ["Ɵ"] = "ɵ",
+ ["Ơ"] = "ơ",
+ ["Ƣ"] = "ƣ",
+ ["Ƥ"] = "ƥ",
+ ["Ʀ"] = "ʀ",
+ ["Ƨ"] = "ƨ",
+ ["Ʃ"] = "ʃ",
+ ["Ƭ"] = "ƭ",
+ ["Ʈ"] = "ʈ",
+ ["Ư"] = "ư",
+ ["Ʊ"] = "ʊ",
+ ["Ʋ"] = "ʋ",
+ ["Ƴ"] = "ƴ",
+ ["Ƶ"] = "ƶ",
+ ["Ʒ"] = "ʒ",
+ ["Ƹ"] = "ƹ",
+ ["Ƽ"] = "ƽ",
+ ["DŽ"] = "dž",
+ ["Dž"] = "dž",
+ ["LJ"] = "lj",
+ ["Lj"] = "lj",
+ ["NJ"] = "nj",
+ ["Nj"] = "nj",
+ ["Ǎ"] = "ǎ",
+ ["Ǐ"] = "ǐ",
+ ["Ǒ"] = "ǒ",
+ ["Ǔ"] = "ǔ",
+ ["Ǖ"] = "ǖ",
+ ["Ǘ"] = "ǘ",
+ ["Ǚ"] = "ǚ",
+ ["Ǜ"] = "ǜ",
+ ["Ǟ"] = "ǟ",
+ ["Ǡ"] = "ǡ",
+ ["Ǣ"] = "ǣ",
+ ["Ǥ"] = "ǥ",
+ ["Ǧ"] = "ǧ",
+ ["Ǩ"] = "ǩ",
+ ["Ǫ"] = "ǫ",
+ ["Ǭ"] = "ǭ",
+ ["Ǯ"] = "ǯ",
+ ["DZ"] = "dz",
+ ["Dz"] = "dz",
+ ["Ǵ"] = "ǵ",
+ ["Ƕ"] = "ƕ",
+ ["Ƿ"] = "ƿ",
+ ["Ǹ"] = "ǹ",
+ ["Ǻ"] = "ǻ",
+ ["Ǽ"] = "ǽ",
+ ["Ǿ"] = "ǿ",
+ ["Ȁ"] = "ȁ",
+ ["Ȃ"] = "ȃ",
+ ["Ȅ"] = "ȅ",
+ ["Ȇ"] = "ȇ",
+ ["Ȉ"] = "ȉ",
+ ["Ȋ"] = "ȋ",
+ ["Ȍ"] = "ȍ",
+ ["Ȏ"] = "ȏ",
+ ["Ȑ"] = "ȑ",
+ ["Ȓ"] = "ȓ",
+ ["Ȕ"] = "ȕ",
+ ["Ȗ"] = "ȗ",
+ ["Ș"] = "ș",
+ ["Ț"] = "ț",
+ ["Ȝ"] = "ȝ",
+ ["Ȟ"] = "ȟ",
+ ["Ƞ"] = "ƞ",
+ ["Ȣ"] = "ȣ",
+ ["Ȥ"] = "ȥ",
+ ["Ȧ"] = "ȧ",
+ ["Ȩ"] = "ȩ",
+ ["Ȫ"] = "ȫ",
+ ["Ȭ"] = "ȭ",
+ ["Ȯ"] = "ȯ",
+ ["Ȱ"] = "ȱ",
+ ["Ȳ"] = "ȳ",
+ ["Ⱥ"] = "ⱥ",
+ ["Ȼ"] = "ȼ",
+ ["Ƚ"] = "ƚ",
+ ["Ⱦ"] = "ⱦ",
+ ["Ɂ"] = "ɂ",
+ ["Ƀ"] = "ƀ",
+ ["Ʉ"] = "ʉ",
+ ["Ʌ"] = "ʌ",
+ ["Ɇ"] = "ɇ",
+ ["Ɉ"] = "ɉ",
+ ["Ɋ"] = "ɋ",
+ ["Ɍ"] = "ɍ",
+ ["Ɏ"] = "ɏ",
+ ["Ά"] = "ά",
+ ["Έ"] = "έ",
+ ["Ή"] = "ή",
+ ["Ί"] = "ί",
+ ["Ό"] = "ό",
+ ["Ύ"] = "ύ",
+ ["Ώ"] = "ώ",
+ ["Α"] = "α",
+ ["Β"] = "β",
+ ["Γ"] = "γ",
+ ["Δ"] = "δ",
+ ["Ε"] = "ε",
+ ["Ζ"] = "ζ",
+ ["Η"] = "η",
+ ["Θ"] = "θ",
+ ["Ι"] = "ι",
+ ["Κ"] = "κ",
+ ["Λ"] = "λ",
+ ["Μ"] = "μ",
+ ["Ν"] = "ν",
+ ["Ξ"] = "ξ",
+ ["Ο"] = "ο",
+ ["Π"] = "π",
+ ["Ρ"] = "ρ",
+ ["Σ"] = "σ",
+ ["Τ"] = "τ",
+ ["Υ"] = "υ",
+ ["Φ"] = "φ",
+ ["Χ"] = "χ",
+ ["Ψ"] = "ψ",
+ ["Ω"] = "ω",
+ ["Ϊ"] = "ϊ",
+ ["Ϋ"] = "ϋ",
+ ["Ϙ"] = "ϙ",
+ ["Ϛ"] = "ϛ",
+ ["Ϝ"] = "ϝ",
+ ["Ϟ"] = "ϟ",
+ ["Ϡ"] = "ϡ",
+ ["Ϣ"] = "ϣ",
+ ["Ϥ"] = "ϥ",
+ ["Ϧ"] = "ϧ",
+ ["Ϩ"] = "ϩ",
+ ["Ϫ"] = "ϫ",
+ ["Ϭ"] = "ϭ",
+ ["Ϯ"] = "ϯ",
+ ["ϴ"] = "θ",
+ ["Ϸ"] = "ϸ",
+ ["Ϲ"] = "ϲ",
+ ["Ϻ"] = "ϻ",
+ ["Ͻ"] = "ͻ",
+ ["Ͼ"] = "ͼ",
+ ["Ͽ"] = "ͽ",
+ ["Ѐ"] = "ѐ",
+ ["Ё"] = "ё",
+ ["Ђ"] = "ђ",
+ ["Ѓ"] = "ѓ",
+ ["Є"] = "є",
+ ["Ѕ"] = "ѕ",
+ ["І"] = "і",
+ ["Ї"] = "ї",
+ ["Ј"] = "ј",
+ ["Љ"] = "љ",
+ ["Њ"] = "њ",
+ ["Ћ"] = "ћ",
+ ["Ќ"] = "ќ",
+ ["Ѝ"] = "ѝ",
+ ["Ў"] = "ў",
+ ["Џ"] = "џ",
+ ["А"] = "а",
+ ["Б"] = "б",
+ ["В"] = "в",
+ ["Г"] = "г",
+ ["Д"] = "д",
+ ["Е"] = "е",
+ ["Ж"] = "ж",
+ ["З"] = "з",
+ ["И"] = "и",
+ ["Й"] = "й",
+ ["К"] = "к",
+ ["Л"] = "л",
+ ["М"] = "м",
+ ["Н"] = "н",
+ ["О"] = "о",
+ ["П"] = "п",
+ ["Р"] = "р",
+ ["С"] = "с",
+ ["Т"] = "т",
+ ["У"] = "у",
+ ["Ф"] = "ф",
+ ["Х"] = "х",
+ ["Ц"] = "ц",
+ ["Ч"] = "ч",
+ ["Ш"] = "ш",
+ ["Щ"] = "щ",
+ ["Ъ"] = "ъ",
+ ["Ы"] = "ы",
+ ["Ь"] = "ь",
+ ["Э"] = "э",
+ ["Ю"] = "ю",
+ ["Я"] = "я",
+ ["Ѡ"] = "ѡ",
+ ["Ѣ"] = "ѣ",
+ ["Ѥ"] = "ѥ",
+ ["Ѧ"] = "ѧ",
+ ["Ѩ"] = "ѩ",
+ ["Ѫ"] = "ѫ",
+ ["Ѭ"] = "ѭ",
+ ["Ѯ"] = "ѯ",
+ ["Ѱ"] = "ѱ",
+ ["Ѳ"] = "ѳ",
+ ["Ѵ"] = "ѵ",
+ ["Ѷ"] = "ѷ",
+ ["Ѹ"] = "ѹ",
+ ["Ѻ"] = "ѻ",
+ ["Ѽ"] = "ѽ",
+ ["Ѿ"] = "ѿ",
+ ["Ҁ"] = "ҁ",
+ ["Ҋ"] = "ҋ",
+ ["Ҍ"] = "ҍ",
+ ["Ҏ"] = "ҏ",
+ ["Ґ"] = "ґ",
+ ["Ғ"] = "ғ",
+ ["Ҕ"] = "ҕ",
+ ["Җ"] = "җ",
+ ["Ҙ"] = "ҙ",
+ ["Қ"] = "қ",
+ ["Ҝ"] = "ҝ",
+ ["Ҟ"] = "ҟ",
+ ["Ҡ"] = "ҡ",
+ ["Ң"] = "ң",
+ ["Ҥ"] = "ҥ",
+ ["Ҧ"] = "ҧ",
+ ["Ҩ"] = "ҩ",
+ ["Ҫ"] = "ҫ",
+ ["Ҭ"] = "ҭ",
+ ["Ү"] = "ү",
+ ["Ұ"] = "ұ",
+ ["Ҳ"] = "ҳ",
+ ["Ҵ"] = "ҵ",
+ ["Ҷ"] = "ҷ",
+ ["Ҹ"] = "ҹ",
+ ["Һ"] = "һ",
+ ["Ҽ"] = "ҽ",
+ ["Ҿ"] = "ҿ",
+ ["Ӏ"] = "ӏ",
+ ["Ӂ"] = "ӂ",
+ ["Ӄ"] = "ӄ",
+ ["Ӆ"] = "ӆ",
+ ["Ӈ"] = "ӈ",
+ ["Ӊ"] = "ӊ",
+ ["Ӌ"] = "ӌ",
+ ["Ӎ"] = "ӎ",
+ ["Ӑ"] = "ӑ",
+ ["Ӓ"] = "ӓ",
+ ["Ӕ"] = "ӕ",
+ ["Ӗ"] = "ӗ",
+ ["Ә"] = "ә",
+ ["Ӛ"] = "ӛ",
+ ["Ӝ"] = "ӝ",
+ ["Ӟ"] = "ӟ",
+ ["Ӡ"] = "ӡ",
+ ["Ӣ"] = "ӣ",
+ ["Ӥ"] = "ӥ",
+ ["Ӧ"] = "ӧ",
+ ["Ө"] = "ө",
+ ["Ӫ"] = "ӫ",
+ ["Ӭ"] = "ӭ",
+ ["Ӯ"] = "ӯ",
+ ["Ӱ"] = "ӱ",
+ ["Ӳ"] = "ӳ",
+ ["Ӵ"] = "ӵ",
+ ["Ӷ"] = "ӷ",
+ ["Ӹ"] = "ӹ",
+ ["Ӻ"] = "ӻ",
+ ["Ӽ"] = "ӽ",
+ ["Ӿ"] = "ӿ",
+ ["Ԁ"] = "ԁ",
+ ["Ԃ"] = "ԃ",
+ ["Ԅ"] = "ԅ",
+ ["Ԇ"] = "ԇ",
+ ["Ԉ"] = "ԉ",
+ ["Ԋ"] = "ԋ",
+ ["Ԍ"] = "ԍ",
+ ["Ԏ"] = "ԏ",
+ ["Ԑ"] = "ԑ",
+ ["Ԓ"] = "ԓ",
+ ["Ա"] = "ա",
+ ["Բ"] = "բ",
+ ["Գ"] = "գ",
+ ["Դ"] = "դ",
+ ["Ե"] = "ե",
+ ["Զ"] = "զ",
+ ["Է"] = "է",
+ ["Ը"] = "ը",
+ ["Թ"] = "թ",
+ ["Ժ"] = "ժ",
+ ["Ի"] = "ի",
+ ["Լ"] = "լ",
+ ["Խ"] = "խ",
+ ["Ծ"] = "ծ",
+ ["Կ"] = "կ",
+ ["Հ"] = "հ",
+ ["Ձ"] = "ձ",
+ ["Ղ"] = "ղ",
+ ["Ճ"] = "ճ",
+ ["Մ"] = "մ",
+ ["Յ"] = "յ",
+ ["Ն"] = "ն",
+ ["Շ"] = "շ",
+ ["Ո"] = "ո",
+ ["Չ"] = "չ",
+ ["Պ"] = "պ",
+ ["Ջ"] = "ջ",
+ ["Ռ"] = "ռ",
+ ["Ս"] = "ս",
+ ["Վ"] = "վ",
+ ["Տ"] = "տ",
+ ["Ր"] = "ր",
+ ["Ց"] = "ց",
+ ["Ւ"] = "ւ",
+ ["Փ"] = "փ",
+ ["Ք"] = "ք",
+ ["Օ"] = "օ",
+ ["Ֆ"] = "ֆ",
+ ["Ⴀ"] = "ⴀ",
+ ["Ⴁ"] = "ⴁ",
+ ["Ⴂ"] = "ⴂ",
+ ["Ⴃ"] = "ⴃ",
+ ["Ⴄ"] = "ⴄ",
+ ["Ⴅ"] = "ⴅ",
+ ["Ⴆ"] = "ⴆ",
+ ["Ⴇ"] = "ⴇ",
+ ["Ⴈ"] = "ⴈ",
+ ["Ⴉ"] = "ⴉ",
+ ["Ⴊ"] = "ⴊ",
+ ["Ⴋ"] = "ⴋ",
+ ["Ⴌ"] = "ⴌ",
+ ["Ⴍ"] = "ⴍ",
+ ["Ⴎ"] = "ⴎ",
+ ["Ⴏ"] = "ⴏ",
+ ["Ⴐ"] = "ⴐ",
+ ["Ⴑ"] = "ⴑ",
+ ["Ⴒ"] = "ⴒ",
+ ["Ⴓ"] = "ⴓ",
+ ["Ⴔ"] = "ⴔ",
+ ["Ⴕ"] = "ⴕ",
+ ["Ⴖ"] = "ⴖ",
+ ["Ⴗ"] = "ⴗ",
+ ["Ⴘ"] = "ⴘ",
+ ["Ⴙ"] = "ⴙ",
+ ["Ⴚ"] = "ⴚ",
+ ["Ⴛ"] = "ⴛ",
+ ["Ⴜ"] = "ⴜ",
+ ["Ⴝ"] = "ⴝ",
+ ["Ⴞ"] = "ⴞ",
+ ["Ⴟ"] = "ⴟ",
+ ["Ⴠ"] = "ⴠ",
+ ["Ⴡ"] = "ⴡ",
+ ["Ⴢ"] = "ⴢ",
+ ["Ⴣ"] = "ⴣ",
+ ["Ⴤ"] = "ⴤ",
+ ["Ⴥ"] = "ⴥ",
+ ["Ḁ"] = "ḁ",
+ ["Ḃ"] = "ḃ",
+ ["Ḅ"] = "ḅ",
+ ["Ḇ"] = "ḇ",
+ ["Ḉ"] = "ḉ",
+ ["Ḋ"] = "ḋ",
+ ["Ḍ"] = "ḍ",
+ ["Ḏ"] = "ḏ",
+ ["Ḑ"] = "ḑ",
+ ["Ḓ"] = "ḓ",
+ ["Ḕ"] = "ḕ",
+ ["Ḗ"] = "ḗ",
+ ["Ḙ"] = "ḙ",
+ ["Ḛ"] = "ḛ",
+ ["Ḝ"] = "ḝ",
+ ["Ḟ"] = "ḟ",
+ ["Ḡ"] = "ḡ",
+ ["Ḣ"] = "ḣ",
+ ["Ḥ"] = "ḥ",
+ ["Ḧ"] = "ḧ",
+ ["Ḩ"] = "ḩ",
+ ["Ḫ"] = "ḫ",
+ ["Ḭ"] = "ḭ",
+ ["Ḯ"] = "ḯ",
+ ["Ḱ"] = "ḱ",
+ ["Ḳ"] = "ḳ",
+ ["Ḵ"] = "ḵ",
+ ["Ḷ"] = "ḷ",
+ ["Ḹ"] = "ḹ",
+ ["Ḻ"] = "ḻ",
+ ["Ḽ"] = "ḽ",
+ ["Ḿ"] = "ḿ",
+ ["Ṁ"] = "ṁ",
+ ["Ṃ"] = "ṃ",
+ ["Ṅ"] = "ṅ",
+ ["Ṇ"] = "ṇ",
+ ["Ṉ"] = "ṉ",
+ ["Ṋ"] = "ṋ",
+ ["Ṍ"] = "ṍ",
+ ["Ṏ"] = "ṏ",
+ ["Ṑ"] = "ṑ",
+ ["Ṓ"] = "ṓ",
+ ["Ṕ"] = "ṕ",
+ ["Ṗ"] = "ṗ",
+ ["Ṙ"] = "ṙ",
+ ["Ṛ"] = "ṛ",
+ ["Ṝ"] = "ṝ",
+ ["Ṟ"] = "ṟ",
+ ["Ṡ"] = "ṡ",
+ ["Ṣ"] = "ṣ",
+ ["Ṥ"] = "ṥ",
+ ["Ṧ"] = "ṧ",
+ ["Ṩ"] = "ṩ",
+ ["Ṫ"] = "ṫ",
+ ["Ṭ"] = "ṭ",
+ ["Ṯ"] = "ṯ",
+ ["Ṱ"] = "ṱ",
+ ["Ṳ"] = "ṳ",
+ ["Ṵ"] = "ṵ",
+ ["Ṷ"] = "ṷ",
+ ["Ṹ"] = "ṹ",
+ ["Ṻ"] = "ṻ",
+ ["Ṽ"] = "ṽ",
+ ["Ṿ"] = "ṿ",
+ ["Ẁ"] = "ẁ",
+ ["Ẃ"] = "ẃ",
+ ["Ẅ"] = "ẅ",
+ ["Ẇ"] = "ẇ",
+ ["Ẉ"] = "ẉ",
+ ["Ẋ"] = "ẋ",
+ ["Ẍ"] = "ẍ",
+ ["Ẏ"] = "ẏ",
+ ["Ẑ"] = "ẑ",
+ ["Ẓ"] = "ẓ",
+ ["Ẕ"] = "ẕ",
+ ["Ạ"] = "ạ",
+ ["Ả"] = "ả",
+ ["Ấ"] = "ấ",
+ ["Ầ"] = "ầ",
+ ["Ẩ"] = "ẩ",
+ ["Ẫ"] = "ẫ",
+ ["Ậ"] = "ậ",
+ ["Ắ"] = "ắ",
+ ["Ằ"] = "ằ",
+ ["Ẳ"] = "ẳ",
+ ["Ẵ"] = "ẵ",
+ ["Ặ"] = "ặ",
+ ["Ẹ"] = "ẹ",
+ ["Ẻ"] = "ẻ",
+ ["Ẽ"] = "ẽ",
+ ["Ế"] = "ế",
+ ["Ề"] = "ề",
+ ["Ể"] = "ể",
+ ["Ễ"] = "ễ",
+ ["Ệ"] = "ệ",
+ ["Ỉ"] = "ỉ",
+ ["Ị"] = "ị",
+ ["Ọ"] = "ọ",
+ ["Ỏ"] = "ỏ",
+ ["Ố"] = "ố",
+ ["Ồ"] = "ồ",
+ ["Ổ"] = "ổ",
+ ["Ỗ"] = "ỗ",
+ ["Ộ"] = "ộ",
+ ["Ớ"] = "ớ",
+ ["Ờ"] = "ờ",
+ ["Ở"] = "ở",
+ ["Ỡ"] = "ỡ",
+ ["Ợ"] = "ợ",
+ ["Ụ"] = "ụ",
+ ["Ủ"] = "ủ",
+ ["Ứ"] = "ứ",
+ ["Ừ"] = "ừ",
+ ["Ử"] = "ử",
+ ["Ữ"] = "ữ",
+ ["Ự"] = "ự",
+ ["Ỳ"] = "ỳ",
+ ["Ỵ"] = "ỵ",
+ ["Ỷ"] = "ỷ",
+ ["Ỹ"] = "ỹ",
+ ["Ἀ"] = "ἀ",
+ ["Ἁ"] = "ἁ",
+ ["Ἂ"] = "ἂ",
+ ["Ἃ"] = "ἃ",
+ ["Ἄ"] = "ἄ",
+ ["Ἅ"] = "ἅ",
+ ["Ἆ"] = "ἆ",
+ ["Ἇ"] = "ἇ",
+ ["Ἐ"] = "ἐ",
+ ["Ἑ"] = "ἑ",
+ ["Ἒ"] = "ἒ",
+ ["Ἓ"] = "ἓ",
+ ["Ἔ"] = "ἔ",
+ ["Ἕ"] = "ἕ",
+ ["Ἠ"] = "ἠ",
+ ["Ἡ"] = "ἡ",
+ ["Ἢ"] = "ἢ",
+ ["Ἣ"] = "ἣ",
+ ["Ἤ"] = "ἤ",
+ ["Ἥ"] = "ἥ",
+ ["Ἦ"] = "ἦ",
+ ["Ἧ"] = "ἧ",
+ ["Ἰ"] = "ἰ",
+ ["Ἱ"] = "ἱ",
+ ["Ἲ"] = "ἲ",
+ ["Ἳ"] = "ἳ",
+ ["Ἴ"] = "ἴ",
+ ["Ἵ"] = "ἵ",
+ ["Ἶ"] = "ἶ",
+ ["Ἷ"] = "ἷ",
+ ["Ὀ"] = "ὀ",
+ ["Ὁ"] = "ὁ",
+ ["Ὂ"] = "ὂ",
+ ["Ὃ"] = "ὃ",
+ ["Ὄ"] = "ὄ",
+ ["Ὅ"] = "ὅ",
+ ["Ὑ"] = "ὑ",
+ ["Ὓ"] = "ὓ",
+ ["Ὕ"] = "ὕ",
+ ["Ὗ"] = "ὗ",
+ ["Ὠ"] = "ὠ",
+ ["Ὡ"] = "ὡ",
+ ["Ὢ"] = "ὢ",
+ ["Ὣ"] = "ὣ",
+ ["Ὤ"] = "ὤ",
+ ["Ὥ"] = "ὥ",
+ ["Ὦ"] = "ὦ",
+ ["Ὧ"] = "ὧ",
+ ["ᾈ"] = "ᾀ",
+ ["ᾉ"] = "ᾁ",
+ ["ᾊ"] = "ᾂ",
+ ["ᾋ"] = "ᾃ",
+ ["ᾌ"] = "ᾄ",
+ ["ᾍ"] = "ᾅ",
+ ["ᾎ"] = "ᾆ",
+ ["ᾏ"] = "ᾇ",
+ ["ᾘ"] = "ᾐ",
+ ["ᾙ"] = "ᾑ",
+ ["ᾚ"] = "ᾒ",
+ ["ᾛ"] = "ᾓ",
+ ["ᾜ"] = "ᾔ",
+ ["ᾝ"] = "ᾕ",
+ ["ᾞ"] = "ᾖ",
+ ["ᾟ"] = "ᾗ",
+ ["ᾨ"] = "ᾠ",
+ ["ᾩ"] = "ᾡ",
+ ["ᾪ"] = "ᾢ",
+ ["ᾫ"] = "ᾣ",
+ ["ᾬ"] = "ᾤ",
+ ["ᾭ"] = "ᾥ",
+ ["ᾮ"] = "ᾦ",
+ ["ᾯ"] = "ᾧ",
+ ["Ᾰ"] = "ᾰ",
+ ["Ᾱ"] = "ᾱ",
+ ["Ὰ"] = "ὰ",
+ ["Ά"] = "ά",
+ ["ᾼ"] = "ᾳ",
+ ["Ὲ"] = "ὲ",
+ ["Έ"] = "έ",
+ ["Ὴ"] = "ὴ",
+ ["Ή"] = "ή",
+ ["ῌ"] = "ῃ",
+ ["Ῐ"] = "ῐ",
+ ["Ῑ"] = "ῑ",
+ ["Ὶ"] = "ὶ",
+ ["Ί"] = "ί",
+ ["Ῠ"] = "ῠ",
+ ["Ῡ"] = "ῡ",
+ ["Ὺ"] = "ὺ",
+ ["Ύ"] = "ύ",
+ ["Ῥ"] = "ῥ",
+ ["Ὸ"] = "ὸ",
+ ["Ό"] = "ό",
+ ["Ὼ"] = "ὼ",
+ ["Ώ"] = "ώ",
+ ["ῼ"] = "ῳ",
+ ["Ω"] = "ω",
+ ["K"] = "k",
+ ["Å"] = "å",
+ ["Ⅎ"] = "ⅎ",
+ ["Ⅰ"] = "ⅰ",
+ ["Ⅱ"] = "ⅱ",
+ ["Ⅲ"] = "ⅲ",
+ ["Ⅳ"] = "ⅳ",
+ ["Ⅴ"] = "ⅴ",
+ ["Ⅵ"] = "ⅵ",
+ ["Ⅶ"] = "ⅶ",
+ ["Ⅷ"] = "ⅷ",
+ ["Ⅸ"] = "ⅸ",
+ ["Ⅹ"] = "ⅹ",
+ ["Ⅺ"] = "ⅺ",
+ ["Ⅻ"] = "ⅻ",
+ ["Ⅼ"] = "ⅼ",
+ ["Ⅽ"] = "ⅽ",
+ ["Ⅾ"] = "ⅾ",
+ ["Ⅿ"] = "ⅿ",
+ ["Ↄ"] = "ↄ",
+ ["Ⓐ"] = "ⓐ",
+ ["Ⓑ"] = "ⓑ",
+ ["Ⓒ"] = "ⓒ",
+ ["Ⓓ"] = "ⓓ",
+ ["Ⓔ"] = "ⓔ",
+ ["Ⓕ"] = "ⓕ",
+ ["Ⓖ"] = "ⓖ",
+ ["Ⓗ"] = "ⓗ",
+ ["Ⓘ"] = "ⓘ",
+ ["Ⓙ"] = "ⓙ",
+ ["Ⓚ"] = "ⓚ",
+ ["Ⓛ"] = "ⓛ",
+ ["Ⓜ"] = "ⓜ",
+ ["Ⓝ"] = "ⓝ",
+ ["Ⓞ"] = "ⓞ",
+ ["Ⓟ"] = "ⓟ",
+ ["Ⓠ"] = "ⓠ",
+ ["Ⓡ"] = "ⓡ",
+ ["Ⓢ"] = "ⓢ",
+ ["Ⓣ"] = "ⓣ",
+ ["Ⓤ"] = "ⓤ",
+ ["Ⓥ"] = "ⓥ",
+ ["Ⓦ"] = "ⓦ",
+ ["Ⓧ"] = "ⓧ",
+ ["Ⓨ"] = "ⓨ",
+ ["Ⓩ"] = "ⓩ",
+ ["Ⰰ"] = "ⰰ",
+ ["Ⰱ"] = "ⰱ",
+ ["Ⰲ"] = "ⰲ",
+ ["Ⰳ"] = "ⰳ",
+ ["Ⰴ"] = "ⰴ",
+ ["Ⰵ"] = "ⰵ",
+ ["Ⰶ"] = "ⰶ",
+ ["Ⰷ"] = "ⰷ",
+ ["Ⰸ"] = "ⰸ",
+ ["Ⰹ"] = "ⰹ",
+ ["Ⰺ"] = "ⰺ",
+ ["Ⰻ"] = "ⰻ",
+ ["Ⰼ"] = "ⰼ",
+ ["Ⰽ"] = "ⰽ",
+ ["Ⰾ"] = "ⰾ",
+ ["Ⰿ"] = "ⰿ",
+ ["Ⱀ"] = "ⱀ",
+ ["Ⱁ"] = "ⱁ",
+ ["Ⱂ"] = "ⱂ",
+ ["Ⱃ"] = "ⱃ",
+ ["Ⱄ"] = "ⱄ",
+ ["Ⱅ"] = "ⱅ",
+ ["Ⱆ"] = "ⱆ",
+ ["Ⱇ"] = "ⱇ",
+ ["Ⱈ"] = "ⱈ",
+ ["Ⱉ"] = "ⱉ",
+ ["Ⱊ"] = "ⱊ",
+ ["Ⱋ"] = "ⱋ",
+ ["Ⱌ"] = "ⱌ",
+ ["Ⱍ"] = "ⱍ",
+ ["Ⱎ"] = "ⱎ",
+ ["Ⱏ"] = "ⱏ",
+ ["Ⱐ"] = "ⱐ",
+ ["Ⱑ"] = "ⱑ",
+ ["Ⱒ"] = "ⱒ",
+ ["Ⱓ"] = "ⱓ",
+ ["Ⱔ"] = "ⱔ",
+ ["Ⱕ"] = "ⱕ",
+ ["Ⱖ"] = "ⱖ",
+ ["Ⱗ"] = "ⱗ",
+ ["Ⱘ"] = "ⱘ",
+ ["Ⱙ"] = "ⱙ",
+ ["Ⱚ"] = "ⱚ",
+ ["Ⱛ"] = "ⱛ",
+ ["Ⱜ"] = "ⱜ",
+ ["Ⱝ"] = "ⱝ",
+ ["Ⱞ"] = "ⱞ",
+ ["Ⱡ"] = "ⱡ",
+ ["Ɫ"] = "ɫ",
+ ["Ᵽ"] = "ᵽ",
+ ["Ɽ"] = "ɽ",
+ ["Ⱨ"] = "ⱨ",
+ ["Ⱪ"] = "ⱪ",
+ ["Ⱬ"] = "ⱬ",
+ ["Ⱶ"] = "ⱶ",
+ ["Ⲁ"] = "ⲁ",
+ ["Ⲃ"] = "ⲃ",
+ ["Ⲅ"] = "ⲅ",
+ ["Ⲇ"] = "ⲇ",
+ ["Ⲉ"] = "ⲉ",
+ ["Ⲋ"] = "ⲋ",
+ ["Ⲍ"] = "ⲍ",
+ ["Ⲏ"] = "ⲏ",
+ ["Ⲑ"] = "ⲑ",
+ ["Ⲓ"] = "ⲓ",
+ ["Ⲕ"] = "ⲕ",
+ ["Ⲗ"] = "ⲗ",
+ ["Ⲙ"] = "ⲙ",
+ ["Ⲛ"] = "ⲛ",
+ ["Ⲝ"] = "ⲝ",
+ ["Ⲟ"] = "ⲟ",
+ ["Ⲡ"] = "ⲡ",
+ ["Ⲣ"] = "ⲣ",
+ ["Ⲥ"] = "ⲥ",
+ ["Ⲧ"] = "ⲧ",
+ ["Ⲩ"] = "ⲩ",
+ ["Ⲫ"] = "ⲫ",
+ ["Ⲭ"] = "ⲭ",
+ ["Ⲯ"] = "ⲯ",
+ ["Ⲱ"] = "ⲱ",
+ ["Ⲳ"] = "ⲳ",
+ ["Ⲵ"] = "ⲵ",
+ ["Ⲷ"] = "ⲷ",
+ ["Ⲹ"] = "ⲹ",
+ ["Ⲻ"] = "ⲻ",
+ ["Ⲽ"] = "ⲽ",
+ ["Ⲿ"] = "ⲿ",
+ ["Ⳁ"] = "ⳁ",
+ ["Ⳃ"] = "ⳃ",
+ ["Ⳅ"] = "ⳅ",
+ ["Ⳇ"] = "ⳇ",
+ ["Ⳉ"] = "ⳉ",
+ ["Ⳋ"] = "ⳋ",
+ ["Ⳍ"] = "ⳍ",
+ ["Ⳏ"] = "ⳏ",
+ ["Ⳑ"] = "ⳑ",
+ ["Ⳓ"] = "ⳓ",
+ ["Ⳕ"] = "ⳕ",
+ ["Ⳗ"] = "ⳗ",
+ ["Ⳙ"] = "ⳙ",
+ ["Ⳛ"] = "ⳛ",
+ ["Ⳝ"] = "ⳝ",
+ ["Ⳟ"] = "ⳟ",
+ ["Ⳡ"] = "ⳡ",
+ ["Ⳣ"] = "ⳣ",
+ ["A"] = "a",
+ ["B"] = "b",
+ ["C"] = "c",
+ ["D"] = "d",
+ ["E"] = "e",
+ ["F"] = "f",
+ ["G"] = "g",
+ ["H"] = "h",
+ ["I"] = "i",
+ ["J"] = "j",
+ ["K"] = "k",
+ ["L"] = "l",
+ ["M"] = "m",
+ ["N"] = "n",
+ ["O"] = "o",
+ ["P"] = "p",
+ ["Q"] = "q",
+ ["R"] = "r",
+ ["S"] = "s",
+ ["T"] = "t",
+ ["U"] = "u",
+ ["V"] = "v",
+ ["W"] = "w",
+ ["X"] = "x",
+ ["Y"] = "y",
+ ["Z"] = "z",
+ ["𐐀"] = "𐐨",
+ ["𐐁"] = "𐐩",
+ ["𐐂"] = "𐐪",
+ ["𐐃"] = "𐐫",
+ ["𐐄"] = "𐐬",
+ ["𐐅"] = "𐐭",
+ ["𐐆"] = "𐐮",
+ ["𐐇"] = "𐐯",
+ ["𐐈"] = "𐐰",
+ ["𐐉"] = "𐐱",
+ ["𐐊"] = "𐐲",
+ ["𐐋"] = "𐐳",
+ ["𐐌"] = "𐐴",
+ ["𐐍"] = "𐐵",
+ ["𐐎"] = "𐐶",
+ ["𐐏"] = "𐐷",
+ ["𐐐"] = "𐐸",
+ ["𐐑"] = "𐐹",
+ ["𐐒"] = "𐐺",
+ ["𐐓"] = "𐐻",
+ ["𐐔"] = "𐐼",
+ ["𐐕"] = "𐐽",
+ ["𐐖"] = "𐐾",
+ ["𐐗"] = "𐐿",
+ ["𐐘"] = "𐑀",
+ ["𐐙"] = "𐑁",
+ ["𐐚"] = "𐑂",
+ ["𐐛"] = "𐑃",
+ ["𐐜"] = "𐑄",
+ ["𐐝"] = "𐑅",
+ ["𐐞"] = "𐑆",
+ ["𐐟"] = "𐑇",
+ ["𐐠"] = "𐑈",
+ ["𐐡"] = "𐑉",
+ ["𐐢"] = "𐑊",
+ ["𐐣"] = "𐑋",
+ ["𐐤"] = "𐑌",
+ ["𐐥"] = "𐑍",
+ ["𐐦"] = "𐑎",
+ ["𐐧"] = "𐑏",
+}
+
+
+return {
+ utf8_lc_uc = utf8_lc_uc,
+ utf8_uc_lc = utf8_uc_lc,
+}
diff --git a/ar/.config/mpv/script-opts/SimpleBookmark.conf b/ar/.config/mpv/script-opts/SimpleBookmark.conf
new file mode 100644
index 0000000..85192f1
--- /dev/null
+++ b/ar/.config/mpv/script-opts/SimpleBookmark.conf
@@ -0,0 +1,311 @@
+######----Settings For SimpleBookmark 1.3.1----######
+####------Script Settings-----####
+##--Filters: (all/keybinds/groups/recents/distinct/protocols/fileonly/titleonly/timeonly/keywords).
+##--Filters description: 'all' to display all the items. Or 'groups' to display the list filtered with items added to any group. Or 'keybinds' to display the list filtered with keybind slots. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file.
+##--Filters can also be stacked by using %+% or omitted by using %-%. e.g.: "groups%+%keybinds" shows only groups and keybinds, "all%-%groups%-%keybinds" shows all items without groups and without keybinds.
+##--Also defined groups can be called by using /:group%Group Name%
+
+#--(none/Filters). Auto run the list when opening mpv and there is no video / file loaded. none for disabled. Or choose between filters.
+auto_run_list_idle=none
+
+#--(0/#number). Runs a saved entry when mpv starts based on its number. -1 for oldest entry. 1 for latest entry. number, e.g.: 13 to load a specific entry. 0 for disabled
+load_item_on_startup=0
+
+#--(yes/no). Hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off)
+toggle_idlescreen=yes
+
+#--(#number). Change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point
+resume_offset=-0.65
+
+#--(yes/no). Display osd messages when actions occur.
+osd_messages=yes
+
+#--(yes/no). When attempting to bookmark, if there is no video / file loaded, it will instead jump to your last bookmarked item
+bookmark_loads_last_idle=yes
+
+#--(yes/no). When attempting to bookmark fileonly, if there is no video / file loaded, it will instead jump to your last bookmarked item without resuming.
+bookmark_fileonly_loads_last_idle=yes
+
+#--(yes/no). Marks the bookmarked time as a chapter
+mark_bookmark_as_chapter=no
+
+#--(yes/no). Preserve video settings when bookmarking items and loading bookmarks by writing mpv watch-later config
+preserve_video_settings=no
+
+#--["keybind","..."]. Keybind that will be used to save the video and its time to log file
+bookmark_save_keybind=["ctrl+'", "ctrl+`"]
+
+#--["keybind","..."]. Keybind that will be used to save the video without time to log file
+bookmark_fileonly_keybind=["", ""]
+
+#--[ ["keybind","Filters"], ["...",""..."] ]. Keybind that will be used to open the list along with the specified filter.
+open_list_keybind=[ ["'", "all"], ["`", "all"], ["", "keybinds"], ["", "keybinds"] ]
+
+#--[ ["keybind","Filters"], ["...",""..."] ]. Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available filters: "all", "keybinds", "recents", "distinct", "protocols", "fileonly", "titleonly", "timeonly", "keywords".
+list_filter_jump_keybind=[ ["b", "all"], ["B", "all"], ["k", "keybinds"], ["K", "keybinds"], ["!", "/:group%TV Shows%"], ["@", "/:group%Movies%"], ["SHARP", "/:group%Anime%"], ["$", "/:group%Anime Movies%"], ["%", "/:group%Cartoon%"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ]
+
+####------Keybind Slots Settings-------####
+
+#--(yes/no). Quick saving a bookmark to keybind slot will not save position
+keybinds_quicksave_fileonly=yes
+
+#--(yes/no). If the keybind slot is empty, this enables quick bookmarking and adding to slot, Otherwise keybinds are assigned from the bookmark list or via quicksave.
+keybinds_empty_auto_create=no
+
+#--(yes/no). When auto creating keybind slot, it will not save position. This config requires "keybinds_empty_auto_create=yes".
+keybinds_empty_fileonly=yes
+
+#--(yes/no). Loading a keybind slot resumes to the bookmarked time.
+keybinds_auto_resume=yes
+
+#--["keybind","..."]. Keybind that will be used to bind list item to a key, as well as to load it. e.g.: Press alt+1 on list cursor position to add it, press alt+1 while list is hidden to load item keybinded into alt+1. (A new slot is automatically created for each keybind. e.g: .."alt+9, alt+0". Where alt+0 creates a new 10th slot.)
+keybinds_add_load_keybind=["", "", "", "", "", "", "", "", ""]
+
+#--["keybind","..."]. To save keybind to a slot without opening the list, to load these keybinds it uses keybinds_add_load_keybind
+keybinds_quicksave_keybind=["", "", "", "", "", "", "", "", ""]
+
+#--["keybind","..."]. Keybind that is used when list is open to remove the keybind slot based on cursor position
+keybinds_remove_keybind=["alt+-"]
+
+#--["keybind","..."]. Keybind that is used when list is open to remove the keybind slot based on highlighted items
+keybinds_remove_highlighted_keybind=["alt+_"]
+
+####------Group Settings-------####
+
+#--["keybind","..."]. Define the groups that can be assigned to a bookmarked item, you can also optionally assign the keybind that puts the bookmarked item into the relevant group when the list is open. Alternatively you can use list_group_add_cycle_keybind to assign item to a group
+groups_list_and_keybind=[ ["TV Shows", "ctrl+1", "ctrl+!"], ["Movies", "ctrl+2", "ctrl+@"], ["Anime", "ctrl+3", "ctrl+#"], ["Anime Movies", "ctrl+4", "ctrl+$"], ["Cartoon", "ctrl+5"], ["Animated Movies"] ]
+
+#--["keybind","..."]. Keybind that is used when list is open to remove the group based on cursor position
+list_groups_remove_keybind=["ctrl+-"]
+
+#--["keybind","..."]. Keybind that is used when list is open to remove the group based on highlighted items
+list_groups_remove_highlighted_keybind=["ctrl+_"]
+
+#--["keybind","..."]. Keybind to add an item to the group, this cycles through all the different available groups when list is open
+list_group_add_cycle_keybind=["ctrl+g"]
+
+#--["keybind","..."]. Keybind to add highlighted items to the group, this cycles through all the different available groups when list is open
+list_group_add_cycle_highlighted_keybind=["ctrl+G"]
+
+####------Logging Settings------####
+
+#--(path). Change to "/:dir%script%" for placing it in the same directory of script, OR change to "/:dir%mpvconf%" for mpv portable_config directory. OR write any variable using "/:var" then the variable "/:var%APPDATA%" you can use path also, such as: "/:var%APPDATA%\mpv" OR "/:var%HOME%/mpv" OR specify the absolute path , e.g.: 'C:\Users\Eisa01\Desktop\'
+log_path=/:dir%mpvconf%
+
+#--(name.extension). of the file that will be used to store the log data
+log_file=mpvBookmark.log
+
+#--(all/protocols/local/none). Store media title in log file, useful for websites / protocols because title cannot be parsed from links alone
+file_title_logging=all
+
+#--["protocol:","..."]. Add below (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = "protocols" or file_title_logging = "all")
+logging_protocols=["https?://", "magnet:", "rtmp:"]
+
+#--(#number). Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry.
+same_entry_limit=-1
+
+#--(yes/no). to preserve groups / slots or any other property when an entry is overwritten.
+overwrite_preserve_properties=yes
+
+
+####------List Settings-------####
+
+#--(yes/no). Going up on the first item loops towards the last item and vise-versa.
+loop_through_list=no
+
+#--(yes/no). Display new entries after reaching the middle of list.
+list_middle_loader=yes
+
+#--(yes/no). Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work)
+quickselect_0to9_keybind=no
+
+#--(yes/no). Exit the list when double tapping the main list, even if the list was accessed through a different filter.
+main_list_keybind_twice_exits=yes
+
+#--(yes/no). To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter.
+search_not_typing_smartly=yes
+
+#--(any/any-notime). 'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results).
+search_behavior=any
+
+####------Filter Settings-------####
+
+#--["Filters","..."]. Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed.
+filters_and_sequence=["all", "keybinds", "groups", "/:group%TV Shows%", "/:group%Movies%", "/:group%Anime%", "/:group%Anime Movies%", "/:group%Cartoon%", "/:group%Animated Movies%", "protocols", "fileonly", "titleonly", "timeonly", "playing", "keywords", "recents", "distinct", "keybinds%+%groups", "all%-%groups%-%keybinds"]
+
+#--["keybind","..."]. Keybind that will be used to go to the next available filter based on the filters_and_sequence
+next_filter_sequence_keybind=["RIGHT", "MBTN_FORWARD"]
+
+#--["keybind","..."]. Keybind that will be used to go to the previous available filter based on the filters_and_sequence
+previous_filter_sequence_keybind=["LEFT", "MBTN_BACK"]
+
+#--(yes/no). Bypass the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa.
+loop_through_filters=yes
+
+#--["string","..."]. Create a filter out of your desired 'keywords', e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]. To disable this filter keep it empty []
+keywords_filter_list=[]
+
+####------Sort Settings-------####
+##--Sorts: added-asc, added-desc, time-asc, time-desc, alphanum-asc, alphanum-desc
+##--Sorts description: 'added-asc' is for the newest added item to show first. Or 'added-desc' for the newest added to show last. Or 'alphanum-asc' is for A to Z approach with filename and episode number lower first. Or 'alphanum-desc' is for its Z to A approach. Or 'time-asc', 'time-desc' to sort the list based on time.
+
+#--(Sorts). Default sorting method for all the different filters in the list. Choose between available sorts.
+list_default_sort=added-asc
+
+#--[ ["Filters","Sorts"], ["...",""..."] ]. Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ]
+list_filters_sort=[ ["keybinds", "keybind-asc"], ["fileonly", "alphanum-asc"], ["playing", "time-asc"] ]
+
+#--["keybind","..."]. Keybind to cycle through the different available sorts when list is open
+list_cycle_sort_keybind=["alt+s", "alt+S"]
+
+####------List Design Settings------####
+
+#--(0-9) The alignment for the list, uses numpad positions choose from 1-9 or 0 to disable. e,g.:7 top left alignment, 8 top middle alignment, 9 top right alignment.
+list_alignment=7
+
+#--(yes/no). Slices long names per the amount specified below
+slice_name=no
+
+#--(#number). Amount for slicing long names (for path, name, and title) list_content_text variables
+slice_name_amount=55
+
+#--(#number). Change maximum number to show items at once
+list_show_amount=10
+
+#--The formatting of the items when you open the list
+#--list_content_text variables: %quickselect%, %number%, %name%, %title%, %path%, %duration%, %length%, %remaining%, %dt%, %dt_"format%"%
+#--Variables explanation: %quickselect%: keybind for quickselect. %number%: numbered sequence of the item position. %name%: shows the file name. %title%: shows file title. %path%: shows the filepath or url. %duration%: the reached playback time of item. %length%: the total time length of the file. %remaining% the remaining playback time of file. %dt%: the logged date and time.
+#--You can also use %dt_"format%"%" as per lua date formatting (https://www.lua.org/pil/22.1.html). It is specified after dt_ ..example: (%dt_%a% %dt_%b% %dt_%y%) for abbreviated day month year
+list_content_text=%number%. %name%%0_duration%%duration%%0_keybind%%keybind%%0_group%%group%%1_group%\h\N\N
+
+#--User defined variables that only displays if the related variable is triggered.
+#--#_group, #_keybind, #_duration, #_length, #_remaining, #_dt. (# represents the possibility of creating many variables using different numbers. e.g.: "0_keybind", "1_keybind")
+list_content_variables=[ ["0_duration", " 🕒 "], ["0_keybind", " ⌨ "], ["0_group", " 🖿 "] ]
+
+#--(string). The text that indicates there are more items above. \N is for new line. \h is for hard space.
+list_sliced_prefix=...\h\N\N
+
+#--(string). The text that indicates there are more items below
+list_sliced_suffix=...
+
+#--(BGR hexadcimal code). Text color for list
+text_color=ffffff
+
+#--(#number). Font size for the text of list
+text_scale=50
+
+#--(#number). Black border size for the text of list
+text_border=0.7
+
+#--(BGR hexadcimal code). Text color of current cursor position
+text_cursor_color=ffbf7f
+
+#--(#number). Font size for text of current cursor position in list
+text_cursor_scale=50
+
+#--(#number). Black border size for text of current cursor position in list
+text_cursor_border=0.7
+
+#--(string). Pre text for highlighted multi-select item
+text_highlight_pre_text=✅
+
+#--(BGR hexadcimal code). Search color when in typing mode
+search_color_typing=00bfff
+
+#--(BGR hexadcimal code). Search color when not in typing mode and it is active
+search_color_not_typing=ffffaa
+
+#--(BGR hexadcimal code). Header color
+header_color=ffffaa
+
+#--(#number). Header text size for the list
+header_scale=55
+
+#--(#number). Black border size for the Header of list
+header_border=0.8
+
+#--Text to be shown as header for the list
+#--header_text variables: %cursor%, %total%, %highlight%, %filter%, %search%, %duration%, %length%, %remaining%.
+#--Variables explanation: %cursor%: the number of cursor position. %total%: total amount in current list. %highlight%: total number of highlighted items. %filter%: shows the filter name, %search%: shows the typed search. %duration%: the total reached playback time of all displayed items. %length%: the total time length of the file for all displayed items. %remaining% the remaining playback time of file for all the displayed items.
+header_text=🔖 Bookmarks [%cursor%/%total%]%0_highlight%%highlight%%0_filter%%filter%%1_filter%%0_sort%%sort%%1_sort%%0_search%%search%%1_search%\h\N\N
+
+#--User defined variables that only displays if the related variable is triggered.
+#--#_filter, #_sort, #_highlight, #_search, #_duration, #_length%, #_remaining. (# represents the possibility of creating many variables using different numbers. e.g.: "0_filter", "1_filter")
+header_variables=[ ["0_highlight", "✅"], ["0_filter", " [Filter: "], ["1_filter", "]"], ["0_sort", " \\{"], ["1_sort", "}"], ["0_search", "\\h\\N\\N[Search="], ["1_search", "..]"] ]
+
+#--(sorts). Sort method that is hidden from header when using %sort% variable
+header_sort_hide_text=added-asc
+
+####-----Time Format Settings-----####
+##--in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability).
+##--in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds.
+##--in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ".
+##--Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5]
+
+osd_time_format=["default", "truncate"]
+list_duration_time_format=["default", "truncate"]
+list_length_time_format=["default", "truncate"]
+list_remaining_time_format=["default", "truncate"]
+header_duration_time_format=["hms", "truncate", ":"]
+header_length_time_format=["hms", "truncate", ":"]
+header_remaining_time_format=["hms", "truncate", ":"]
+
+####------List Keybind Settings------####
+#--Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind
+#--Example: ["alt+b"] / ["b", "B"] / ["a" "ctrl+a", "alt+a"]
+
+#--Keybind that will be used to navigate up on the list
+list_move_up_keybind=["UP", "WHEEL_UP"]
+
+#--Keybind that will be used to navigate down on the list
+list_move_down_keybind=["DOWN", "WHEEL_DOWN"]
+
+#--Keybind that will be used to go to the first item for the page shown on the list
+list_page_up_keybind=["PGUP"]
+
+#--Keybind that will be used to go to the last item for the page shown on the list
+list_page_down_keybind=["PGDWN"]
+
+#--Keybind that will be used to navigate to the first item on the list
+list_move_first_keybind=["HOME"]
+
+#--Keybind that will be used to navigate to the last item on the list
+list_move_last_keybind=["END"]
+
+#--Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc..
+list_highlight_move_keybind=["SHIFT"]
+
+#--Keybind that will be used to highlight all displayed items on the list
+list_highlight_all_keybind=["ctrl+a", "ctrl+A"]
+
+#--Keybind that will be used to remove all currently highlighted items from the list
+list_unhighlight_all_keybind=["ctrl+d", "ctrl+D"]
+
+#--Keybind that will be used to load entry based on cursor position
+list_select_keybind=["ENTER", "MBTN_MID"]
+
+#--Keybind that will be used to add entry to playlist based on cursor position
+list_add_playlist_keybind=["CTRL+ENTER"]
+
+#--Keybind that will be used to add all highlighted entries to playlist
+list_add_playlist_highlighted_keybind=["SHIFT+ENTER"]
+
+#--Keybind that will be used to close the list (closes search first if it is open)
+list_close_keybind=["ESC", "MBTN_RIGHT"]
+
+#--Keybind that will be used to delete the entry based on cursor position
+list_delete_keybind=["DEL"]
+
+#--Keybind that will be used to delete all highlighted entries from the list
+list_delete_highlighted_keybind=["SHIFT+DEL"]
+
+#--Keybind that will be used to trigger search
+list_search_activate_keybind=["ctrl+f", "ctrl+F"]
+
+#--Keybind that will be used to exit typing mode of search while keeping search open
+list_search_not_typing_mode_keybind=["ALT+ENTER"]
+
+#--Keybind thats are ignored when list is open
+list_ignored_keybind=["h", "H", "r", "R", "c", "C"]
+
+######----End of Settings----######
diff --git a/ar/.config/mpv/script-opts/SmartCopyPaste_II.conf b/ar/.config/mpv/script-opts/SmartCopyPaste_II.conf
new file mode 100644
index 0000000..d0fd99a
--- /dev/null
+++ b/ar/.config/mpv/script-opts/SmartCopyPaste_II.conf
@@ -0,0 +1,343 @@
+######----Settings For SmartCopyPaste_II 3.1----######
+####------Script Settings-----####
+
+#--auto is for automatic device detection, or manually change to: windows or mac or linux
+device=auto
+
+#--copy command that will be used in Linux. OR write a different command
+linux_copy=xclip -silent -selection clipboard -in
+
+#--paste command that will be used in Linux. OR write a different command
+linux_paste=xclip -selection clipboard -o
+
+#--copy command that will be used in MAC. OR write a different command
+mac_copy=pbcopy
+
+#--paste command that will be used in MAC. OR write a different command
+mac_paste=pbpaste
+
+#--powershell is for using windows powershell to copy. OR write the copy command, e.g:clip
+windows_copy=powershell
+
+#--powershell is for using windows powershell to paste. OR write the paste command
+windows_paste=powershell
+
+#--Auto run the list when opening mpv and there is no video / file loaded. 'none' for disabled. Or choose between: all, copy, paste, recents, distinct, protocols, fileonly, titleonly, timeonly, keywords.
+auto_run_list_idle=none
+
+#--hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off)
+toggle_idlescreen=no
+
+#--change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point
+resume_offset=-0.65
+
+#--yes is for displaying osd messages when actions occur. Change to no will disable all osd messages generated from this script
+osd_messages=yes
+
+#--yes is for marking the time as a chapter. no disables mark as chapter behavior.
+mark_clipboard_as_chapter=no
+
+#--Option to copy time with video. Select between: local, protocols, specifics, all, none. 'none' for disabled, 'all' to copy time for all videos, 'protocols' for copying time only for protocols, 'specifics' to copy time only for websites defined below, 'local' to copy time for videos that are not protocols
+copy_time_method=all
+
+#--Behavior of paste when nothing valid is copied, and no video is running. select between: force, force-noresume
+log_paste_idle_behavior=force-noresume
+
+#--Behavior of paste when nothing valid is copied, and a video is running. select between: timestamp>playlist, timestamp>force, timestamp, playlist, force, force-noresume
+log_paste_running_behavior=timestamp>playlist
+
+#--The time attributes which will be added when copying protocols of specific websites from this list. Additional attributes can be added following the same format.
+specific_time_attributes=[ ["twitter", "?t=", ""], ["twitch", "?t=", "s"], ["youtube", "&t=", "s"] ]
+
+#--The text that will be copied before the seek time when copying a protocol video from mpv
+protocols_time_attribute=&t=
+
+#--The text that will be copied before the seek time when copying a local video from mpv
+local_time_attribute=&time=
+
+#--The time attributes that can be pasted for resume, specific_time_attributes, protocols_time_attribute, local_time_attribute are automatically added
+pastable_time_attributes=[" | time="]
+
+#--Keybind that will be used to copy
+copy_keybind=["ctrl+y", "ctrl+Y", "meta+y", "meta+Y"]
+
+#--The priority of paste behavior when a video is running. Select between: playlist, timestamp, force.
+running_paste_behavior=playlist
+
+#--Keybind that will be used to paste
+paste_keybind=["ctrl+p", "ctrl+P", "meta+p", "meta+P"]
+
+#--Copy behavior when using copy_specific_keybind. Select between: title, path, timestamp, path&timestamp.
+copy_specific_behavior=path
+
+#--Keybind that will be used to copy based on the copy behavior specified
+copy_specific_keybind=["ctrl+alt+y", "ctrl+alt+Y", "meta+alt+y", "meta+alt+Y"]
+
+#--Paste behavior when using paste_specific_keybind. Select between: playlist, timestamp, force.
+paste_specific_behavior=playlist
+
+#--Keybind that will be used to paste based on the paste behavior specified
+paste_specific_keybind=["ctrl+alt+y", "ctrl+alt+Y", "meta+alt+y", "meta+alt+Y"]
+
+#--add below (after a comma) any protocol you want paste to work with; e.g: ,'ftp://'. Or set it as "" by deleting all defined protocols to make paste works with any protocol.
+paste_protocols=["https?://", "magnet:", "rtmp:", "file:"]
+
+#--add below (after a comma) any extension you want paste to work with; e.g: ,'pdf'. Or set it as "" by deleting all defined extension to make paste works with any extension.
+paste_extensions=["ac3", "a52", "eac3", "mlp", "dts", "dts-hd", "dtshd", "yes-hd", "thd", "yeshd", "thd+ac3", "tta", "pcm", "wav", "aiff", "aif", "aifc", "amr", "awb", "au", "snd", "lpcm", "yuv", "y4m", "ape", "wv", "shn", "m2ts", "m2t", "mts", "mtv", "ts", "tsv", "tsa", "tts", "trp", "adts", "adt", "mpa", "m1a", "m2a", "mp1", "mp2", "mp3", "mpeg", "mpg", "mpe", "mpeg2", "m1v", "m2v", "mp2v", "mpv", "mpv2", "mod", "tod", "vob", "vro", "evob", "evo", "mpeg4", "m4v", "mp4", "mp4v", "mpg4", "m4a", "aac", "h264", "avc", "x264", "264", "hevc", "h265", "x265", "265", "flac", "oga", "ogg", "opus", "spx", "ogv", "ogm", "ogx", "mkv", "mk3d", "mka", "webm", "weba", "avi", "vfw", "divx", "3iv", "xvid", "nut", "flic", "fli", "flc", "nsv", "gxf", "mxf", "wma", "wm", "wmv", "asf", "dvr-ms", "dvr", "wtv", "dv", "hdv", "flv", "f4v", "f4a", "qt", "mov", "hdmov", "rm", "rmvb", "ra", "ram", "3ga", "3ga2", "3gpp", "3gp", "3gp2", "3g2", "ay", "gbs", "gym", "hes", "kss", "nsf", "nsfe", "sap", "spc", "vgm", "vgz", "m3u", "m3u8", "pls", "cue", "ase", "art", "bmp", "blp", "cd5", "cit", "cpt", "cr2", "cut", "dds", "dib", "djvu", "egt", "exif", "gif", "gpl", "grf", "icns", "ico", "iff", "jng", "jpeg", "jpg", "jfif", "jp2", "jps", "lbm", "max", "miff", "mng", "msp", "nitf", "ota", "pbm", "pc1", "pc2", "pc3", "pcf", "pcx", "pdn", "pgm", "PI1", "PI2", "PI3", "pict", "pct", "pnm", "pns", "ppm", "psb", "psd", "pdd", "psp", "px", "pxm", "pxr", "qfx", "raw", "rle", "sct", "sgi", "rgb", "int", "bw", "tga", "tiff", "tif", "vtf", "xbm", "xcf", "xpm", "3dv", "amf", "ai", "awg", "cgm", "cdr", "cmx", "dxf", "e2d", "egt", "eps", "fs", "gbr", "odg", "svg", "stl", "vrml", "x3d", "sxd", "v2d", "vnd", "wmf", "emf", "art", "xar", "png", "webp", "jxr", "hdp", "wdp", "cur", "ecw", "iff", "lbm", "liff", "nrrd", "pam", "pcx", "pgf", "sgi", "rgb", "rgba", "bw", "int", "inta", "sid", "ras", "sun", "tga", "torrent"]
+
+#--add below (after a comma) any extension you want paste to attempt to add as a subtitle file, e.g.:'txt'. Or set it as "" by deleting all defined extension to make paste attempt to add any subtitle.
+paste_subtitles=["aqt", "gsub", "jss", "sub", "ttxt", "pjs", "psb", "rt", "smi", "slt", "ssf", "srt", "ssa", "ass", "usf", "idx", "vtt"]
+
+#--Keybind that will be used to open the list along with the specified filter. Available filters: "all", "copy", "paste", "recents", "distinct", "protocols", "fileonly", "titleonly", "timeonly", "keywords".
+open_list_keybind=[ ["y", "all"], ["Y", "all"] ]
+
+#--Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available filters: "all", "copy", "paste", "recents", "distinct", "protocols", "fileonly", "titleonly", "timeonly", "keywords".
+list_filter_jump_keybind=[ ["y", "all"], ["Y", "all"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ]
+
+####------Logging Settings------####
+
+#--Change to "/:dir%script%" for placing it in the same directory of script, OR change to "/:dir%mpvconf%" for mpv portable_config directory. OR write any variable using "/:var" then the variable "/:var%APPDATA%" you can use path also, such as: "/:var%APPDATA%\mpv" OR "/:var%HOME%/mpv" OR specify the absolute path , e.g.: 'C:\Users\Eisa01\Desktop\'
+log_path=/:dir%mpvconf%
+
+#--name+extension of the file that will be used to store the log data
+log_file=mpvClipboard.log
+
+#--Date format in the log (see lua date formatting), e.g.:"%d/%m/%y %X" or "%d/%b/%y %X"
+date_format=%A/%B %d/%m/%Y %X
+
+#--Change between all, protocols, none. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone
+file_title_logging=protocols
+
+#--add below (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = "protocols" or file_title_logging = "all")
+logging_protocols=["https?://", "magnet:", "rtmp:"]
+
+#--Prefers to copy and log filename over filetitle. Select between: local, protocols, all, none. 'local' prefer filenames for videos that are not protocols. 'protocols' will prefer filenames for protocols only. 'all' will prefer filename over filetitle for both protocols and not protocols videos. 'none' will always use filetitle instead of filename
+prefer_filename_over_title=local
+
+#--Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry.
+same_entry_limit=4
+
+####------List Settings-------####
+
+#--yes is for going up on the first item loops towards the last item and vise-versa. no disables this behavior.
+loop_through_list=no
+
+#--no is for more items to show, then u must reach the end. yes is for new items to show after reaching the middle of list.
+list_middle_loader=yes
+
+#--Show file paths instead of media-title
+show_paths=no
+
+#--Show the number of each item before displaying its name and values.
+show_item_number=yes
+
+#--Change to yes or no. Slices long filenames per the amount specified below
+slice_longfilenames=no
+
+#--Amount for slicing long filenames
+slice_longfilenames_amount=55
+
+#--Change maximum number to show items at once
+list_show_amount=10
+
+#--Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work)
+quickselect_0to9_keybind=yes
+
+#--Will exit the list when double tapping the main list, even if the list was accessed through a different filter.
+main_list_keybind_twice_exits=yes
+
+#--To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter.
+search_not_typing_smartly=yes
+
+#--"specific" to find a match of either a date, title, path / url, time. "any" to find any typed search based on combination of date, title, path / url, and time. "any-notime" to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results).
+search_behavior=any
+
+####------Filter Settings-------####
+##--available filters: "all" to display all the items. Or "copy" to display copied items. Or "paste" to display pasted items. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file.
+
+#--Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed.
+filters_and_sequence=["all", "copy", "paste", "recents", "distinct", "protocols", "playing", "fileonly", "titleonly", "keywords"]
+
+#--Keybind that will be used to go to the next available filter based on the filters_and_sequence
+next_filter_sequence_keybind=["RIGHT", "MBTN_FORWARD"]
+
+#--Keybind that will be used to go to the previous available filter based on the filters_and_sequence
+previous_filter_sequence_keybind=["LEFT", "MBTN_BACK"]
+
+#--yes is for bypassing the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa. no disables this behavior.
+loop_through_filters=yes
+
+#--Create a filter out of your desired "keywords", e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]
+keywords_filter_list=[""]
+
+####------Sort Settings-------####
+##--available sort: added-asc is for the newest added item to show first. Or added-desc for the newest added to show last. Or alphanum-asc is for A to Z approach with filename and episode number lower first. Or alphanum-desc is for its Z to A approach. Or time-asc, time-desc to sort the list based on time.
+
+#--the default sorting method for all the different filters in the list. select between: added-asc, added-desc, time-asc, time-desc, alphanum-asc, alphanum-desc
+list_default_sort=added-asc
+
+#--Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ]
+list_filters_sort=[ ]
+
+#--Keybind to cycle through the different available sorts when list is open
+list_cycle_sort_keybind=["alt+s", "alt+S"]
+
+####------List Design Settings------####
+
+#--The alignment for the list, uses numpad positions choose from 1-9 or 0 to disable. e,g.:7 top left alignment, 8 top middle alignment, 9 top right alignment.
+list_alignment=7
+
+#--The time type for items on the list. Select between: duration, length, remaining.
+text_time_type=duration
+
+#--Time seperator that will be used before the saved time
+time_seperator= 🕒
+
+#--The text that indicates there are more items above. \N is for new line. \h is for hard space.
+list_sliced_prefix=...\h\N\N
+
+#--The text that indicates there are more items below
+list_sliced_suffix=...
+
+#--yes enables pre text for showing quickselect keybinds before the list. no to disable
+quickselect_0to9_pre_text=no
+
+#--Text color for list in BGR hexadecimal
+text_color=ffffff
+
+#--Font size for the text of list
+text_scale=50
+
+#--Black border size for the text of list
+text_border=0.7
+
+#--Text color of current cursor position in BGR
+text_cursor_color=ffbf7f
+
+#--Font size for text of current cursor position in list
+text_cursor_scale=50
+
+#--Black border size for text of current cursor position in list
+text_cursor_border=0.7
+
+#--Pre text for highlighted multi-select item
+text_highlight_pre_text=✅
+
+#--Search color when in typing mode
+search_color_typing=ffffaa
+
+#--Search color when not in typing mode and it is active
+search_color_not_typing=56ffaa
+
+#--Header color in BGR hexadecimal
+header_color=56ffaa
+
+#--Header text size for the list
+header_scale=55
+
+#--Black border size for the Header of list
+header_border=0.8
+
+#--Text to be shown as header for the list
+#--Available header variables: %cursor%, %total%, %highlight%, %filter%, %search%, %listduration%, %listlength%, %listremaining%
+#--User defined text that only displays if a variable is triggered: %prefilter%, %afterfilter%, %prehighlight%, %afterhighlight% %presearch%, %aftersearch%, %prelistduration%, %afterlistduration%, %prelistlength%, %afterlistlength%, %prelistremaining%, %afterlistremaining%
+#--Variables explanation: %cursor: displays the number of cursor position in list. %total: total amount of items in current list. %highlight%: total number of highlighted items. %filter: shows the filter name, %search: shows the typed search. Example of user defined text that only displays if a variable is triggered of user: %prefilter: user defined text before showing filter, %afterfilter: user defined text after showing filter.
+
+header_text=📋 Clipboard [%cursor%/%total%]%prehighlight%%highlight%%afterhighlight%%prefilter%%filter%%afterfilter%%presort%%sort%%aftersort%%presearch%%search%%aftersearch%
+
+#--Sort method that is hidden from header when using %sort% variable
+header_sort_hide_text=added-asc
+
+#--Text to be shown before or after triggered variable in the header
+header_sort_pre_text= \{
+header_sort_after_text=}
+header_filter_pre_text= [Filter:
+header_filter_after_text=]
+header_search_pre_text=\h\N\N[Search=
+header_search_after_text=..]
+header_highlight_pre_text=✅
+header_highlight_after_text=
+header_list_duration_pre_text= 🕒
+header_list_duration_after_text=
+header_list_length_pre_text= 🕒
+header_list_length_after_text=
+header_list_remaining_pre_text= 🕒
+header_list_remaining_after_text=
+
+#--Copy seperator that will be shown for copied items in the list
+copy_seperator= ©
+
+#--Paste seperator that will be shown for pasted item in the list
+paste_seperator= ℗
+
+####-----Time Format Settings-----####
+##--in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability).
+##--in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds.
+##--in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5]
+
+copy_time_format=["timestamp-concise"]
+osd_time_format=["default", "truncate"]
+list_time_format=["default", "truncate"]
+header_duration_time_format=["hms", "truncate", ":"]
+header_length_time_format=["hms", "truncate", ":"]
+header_remaining_time_format=["hms", "truncate", ":"]
+
+####------List Keybind Settings------####
+#--Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind
+#--Example of changing and adding keybinds: --From ["b", "B"] To ["b"]. --From [""] to ["alt+b"]. --From [""] to ["a" "ctrl+a", "alt+a"]
+
+#--Keybind that will be used to navigate up on the list
+list_move_up_keybind=["UP", "WHEEL_UP"]
+
+#--Keybind that will be used to navigate down on the list
+list_move_down_keybind=["DOWN", "WHEEL_DOWN"]
+
+#--Keybind that will be used to go to the first item for the page shown on the list
+list_page_up_keybind=["PGUP"]
+
+#--Keybind that will be used to go to the last item for the page shown on the list
+list_page_down_keybind=["PGDWN"]
+
+#--Keybind that will be used to navigate to the first item on the list
+list_move_first_keybind=["HOME"]
+
+#--Keybind that will be used to navigate to the last item on the list
+list_move_last_keybind=["END"]
+
+#--Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc..
+list_highlight_move_keybind=["SHIFT"]
+
+#--Keybind that will be used to highlight all displayed items on the list
+list_highlight_all_keybind=["ctrl+a", "ctrl+A"]
+
+#--Keybind that will be used to remove all currently highlighted items from the list
+list_unhighlight_all_keybind=["ctrl+d", "ctrl+D"]
+
+#--Keybind that will be used to load entry based on cursor position
+list_select_keybind=["ENTER", "MBTN_MID"]
+
+#--Keybind that will be used to add entry to playlist based on cursor position
+list_add_playlist_keybind=["CTRL+ENTER"]
+
+#--Keybind that will be used to add all highlighted entries to playlist
+list_add_playlist_highlighted_keybind=["SHIFT+ENTER"]
+
+#--Keybind that will be used to close the list (closes search first if it is open)
+list_close_keybind=["ESC", "MBTN_RIGHT"]
+
+#--Keybind that will be used to delete the entry based on cursor position
+list_delete_keybind=["DEL"]
+
+#--Keybind that will be used to delete all highlighted entries from the list
+list_delete_highlighted_keybind=["SHIFT+DEL"]
+
+#--Keybind that will be used to trigger search
+list_search_activate_keybind=["ctrl+f", "ctrl+F"]
+
+#--Keybind that will be used to exit typing mode of search while keeping search open
+list_search_not_typing_mode_keybind=["ALT+ENTER"]
+
+#--Keybind thats are ignored when list is open
+list_ignored_keybind=["h", "H", "r", "R", "b", "B", "k", "K"]
+
+######----End of Settings----######
diff --git a/ar/.config/mpv/script-opts/SmartSkip.conf b/ar/.config/mpv/script-opts/SmartSkip.conf
new file mode 100644
index 0000000..7d9b33a
--- /dev/null
+++ b/ar/.config/mpv/script-opts/SmartSkip.conf
@@ -0,0 +1,221 @@
+######----Settings For SmartSkip 1.2----######
+####------Script Settings-----####
+
+
+####------Silence Skip Settings-------####
+#--(#number). Maximum amount of noise to trigger, in terms of dB. Lower is more sensitive.
+silence_audio_level=-40
+
+#--(#number). Duration of the silence that will be detected to trigger skipping.
+silence_duration=0.65
+
+#--(0/#number). The detected silences will be ignored for the below defined seconds, and it will continue skipping until a silence is detected that surpasses the ignore duration.
+# (0 for disabled, or specify seconds).
+ignore_silence_duration=5
+
+#--(0/#number). Minimum amount of seconds accepted to skip until the configured silence_duration.
+# (0 for disabled, or specify seconds)
+min_skip_duration=0
+
+#--(0/#number). Maximum amount of seconds accepted to skip until the configured silence_duration.
+# (0 for disabled, or specify seconds)
+max_skip_duration=130
+
+#--(yes/no). Press keybind again while silence-skip is active to cancel
+keybind_twice_cancel_skip=yes
+
+#--(playlist-next/cancel/pause). If skip reaches playback end, and there is no silence detected.
+# (playlist-next: continues towards next playlist item / cancel: cancels seek and resumes from original position / pause: reaches end and pauses).
+silence_skip_to_end=playlist-next
+
+#-- After detecing silence, a chapter will be added.
+# (yes/no). Or specify types ["no-chapters", "internal-chapters", "external-chapters"].
+# e.g.: add_chapter_on_skip=["no-chapters", "external-chapters"]
+add_chapter_on_skip=yes
+
+#--(yes/no). Default is muted, however if audio was enabled due to custom mpv settings, the fast-forwarded audio can sound jarring.
+force_mute_on_skip=no
+
+
+####------Smart Skip Settings-------####
+#--The skip behavior after passing the last chapter
+# (defaults to silence-skip if not defined for a chapter type)
+# Available chapter types (no-chapters, internal-chapters, external-chapters)
+# Available skip types (silence-skip, chapter-next, playlist-next)
+# e.g.: last_chapter_skip_behavior=[ ["no-chapters", "silence-skip"], ["internal-chapters", "playlist-next"], ["external-chapters", "chapter-next"] ].
+last_chapter_skip_behavior=[ ["no-chapters", "silence-skip"], ["internal-chapters", "playlist-next"], ["external-chapters", "silence-skip"] ]
+
+#--(yes/no). During autoskip countdown, pressing smart_next keybind will auto-skip
+smart_next_proceed_countdown=yes
+
+#--(yes/no). During autoskip countdown, pressing smart_prev keybind will cancel the countdown to autoskip
+smart_prev_cancel_countdown=yes
+
+####------Chapters Settings-------####
+#--(yes/no). Automatically loads external chapters when detected.
+# priority: 1. chapters file in the same directory as the playing file, 2. hashed version of the chapters file in the global directory, 3. path based version of the chapters file in the global directory
+external_chapters_autoload=yes
+
+#-- Modifying chapters using SmartSkip will autosave as external-chapters
+# (yes/no). Or specify types ["no-chapters", "internal-chapters", "external-chapters"]
+modified_chapters_autosave=["no-chapters", "external-chapters"]
+
+#--(yes/no). (yes: Saves all chapter files in a single global directory). (no: saves chapter next to the playback file)
+global_chapters=yes
+
+#--(path) Global path for storing external chapters
+#- Predefined directories (/:dir%script%, /:dir%mpvconf%)
+# (/:dir%script%) for placing it in the same directory of script,
+# (/:dir%mpvconf%) for mpv portable_config directory
+#- Or write any variable using "/:var" then the variable
+# (/:var) example: "/:var%APPDATA%", such as: "/:var%APPDATA%\mpv" OR "/:var%HOME%/mpv"
+#- It is also possible to mix and match path with predefined directories / variables
+global_chapters_path=/:dir%mpvconf%/chapters
+
+#--(yes/no). Hashes the chapters saved in the global directory, allowing for multiple files with the same name to have external chapters.
+hash_global_chapters=yes
+
+#--(yes/no). (yes: Adding chapter asks for title) (no: Adds chapter without name)
+add_chapter_ask_title=no
+
+#--(yes/no). Pauses the playback when asking for chapter title
+add_chapter_pause_for_input=no
+
+#--(text). Placeholder when asking for title of a new chapter
+add_chapter_placeholder_title=Chapter
+
+####------Auto-Skip Settings-------####
+
+#--(yes/no). Allows for chapters to be skipped automatically based on configured categories and skip
+autoskip_chapter=yes
+
+#--(0/#number). Countdown in seconds before initiating autoskip
+# (0 disables countdown and immediately initiates autoskip, or specify countdown in seconds)
+autoskip_countdown=3
+
+#--(yes/no). Bulk consecutive autoskip chapters together in 1 countdown
+autoskip_countdown_bulk=no
+
+#--(yes/no). Sends prompt for autoskip without forcing
+autoskip_countdown_graceful=no
+
+#-- Detected autoskip will be triggered once only for each chapter
+# (yes/no). Or specify types ["internal-chapters", "external-chapters"]
+skip_once=no
+
+#--(string) write the string for any chapter type following the syntax:
+# e.g.: categories=my-new-category>Part [AB]/Ending 1; another-category>Part C/Part D
+#-- Or specify categories string for each chapter type.
+# e.g.: categories=[ ["internal-chapters", "my-new-category>Part [AB]/Ending 1; another-category>Part C/Part D"], ["external-chapters", "idx->0/1/2/3"] ]
+# Syntax: category-name-1>chapter-name-1-slash-separated/chapter-name-2-slash-separated; lua patterns, each category is separated by semicolons
+# Predefined Category: (idx- followed by the chapter index / slash separated to autoskip based on index)
+categories=[ ["internal-chapters", "prologue>Prologue/^Intro; opening>^OP/ OP$/^Opening; ending>^ED/ ED$/^Ending; preview>Preview$"], ["external-chapters", "idx->0/2"] ]
+
+#--(string) enables defined categories for autoskip
+# e.g: skip=opening;ending
+# Or write the category names for each chapter type:
+# e.g.: skip=[ ["internal-chapters", "prologue;ending"], ["external-chapters", "idx-"] ] ]]
+# Syntax: To enable category for autoskip, write any defined category name inside the string, followed by a semicolons.
+
+##-- Predefined Categories: (toggle, toggle_idx, idx-)
+# (idx- is for enabling index based autoskip).
+# (toggle is for chapters toggled during playback).
+# (toggle_idx is for chapter index toggled during playback)
+
+skip=[ ["internal-chapters", "toggle;toggle_idx;opening;ending;preview"], ["external-chapters", "toggle;toggle_idx"] ]
+
+####------Autoload Settings-------####
+#--(yes/no). Autoload files in the same directory into playlist
+autoload_playlist=no
+
+#--(#number). Max entries for autoload * 2 when starting a file (before + after). 5000 is the maximum recommended value
+autoload_max_entries=5000
+
+#--(#number). Max directory stack for autoload when starting a file. 20 is the recommended value
+autoload_max_dir_stack=20
+
+#--(yes/no). Hidden files in the directory will be ignored
+ignore_hidden=yes
+
+#--(yes/no). Allow same type of extensions
+same_type=no
+
+#--(auto/recursive/lazy/ignore). Specify the directory mode for autoload
+directory_mode=auto
+
+#--(yes/no). Types that will be autoloaded
+images=yes
+videos=yes
+audio=yes
+
+#--(extension,). Add additional extensions to be autoloaded.
+# e.g.: additional_image_exts=bmp,vob,pxr
+additional_image_exts=
+additional_video_exts=
+additional_audio_exts=
+
+####------OSD Messages Settings-------####
+
+#--(-1/milliseconds). Duration for the osd message in milliseconds, applies to all osd_messages.
+# -1 reverts to mpv --osd-duration
+osd_duration=2500
+
+#-- (no-osd/osd-bar/osd-msg/osd-msg-bar). OSD message that will be shown when the script triggers an action
+seek_osd=osd-msg-bar
+chapter_osd=osd-msg-bar
+autoskip_osd=osd-msg-bar
+
+#--(yes/no). Shows OSD when playlist entry changes
+playlist_osd=yes
+
+#--(yes/no). All other OSD messages.
+osd_msg=yes
+
+####------Keybind Settings-------####
+#-- Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind
+# e.g.: ["ctrl+x"], e.g.2: ["atl+n", "n", "N"]
+
+#-- Enables or disables autoload during playback for the session
+toggle_autoload_keybind=[""]
+
+#-- Enables or disables Auto-Skip during playback for the session
+toggle_autoskip_keybind=[""]
+
+#-- Enables or disables a chapter for Auto-Skip during playback for the session
+toggle_category_autoskip_keybind=[""]
+
+#-- Cancels Auto-Skip when countdown is started
+cancel_autoskip_countdown_keybind=["esc", "n"]
+
+#-- Proceeds Auto-Skip when countdown is started
+proceed_autoskip_countdown_keybind=["enter", "y"]
+
+#-- Add a chapter for the reached position
+add_chapter_keybind=[""]
+
+#-- Removes the current chapter
+remove_chapter_keybind=[""]
+
+#-- Renames the current chapter
+edit_chapter_keybind=[""]
+
+#-- Manually save the changes for chapters using an external file
+write_chapters_keybind=[""]
+
+#-- Merge the changes of chapters for the file inside mkv file
+bake_chapters_keybind=[""]
+
+#-- Jumps to next chapter > to next playlist
+chapter_next_keybind=[""]
+
+#-- Jumps to previous chapter > to begining > to previous playlist
+chapter_prev_keybind=[""]
+
+#-- Triggers silence_skip > next chapter > next playlist based on different variables
+smart_next_keybind=[""]
+
+#-- Jumps to begining > previous chapter > previous playlist based on different variables
+smart_prev_keybind=[""]
+
+#-- Triggers silence skip to detect silence as per the configured parameters
+silence_skip_keybind=[""]
diff --git a/ar/.config/mpv/script-opts/command_palette.conf b/ar/.config/mpv/script-opts/command_palette.conf
new file mode 100644
index 0000000..a913238
--- /dev/null
+++ b/ar/.config/mpv/script-opts/command_palette.conf
@@ -0,0 +1,19 @@
+font_size=40
+scale_by_window=no
+lines_to_show=12
+
+# might be buggy
+#pause_on_open=no
+
+#resume_on_exit=only-if-was-paused
+
+# styles
+#line_bottom_margin=1
+#menu_x_padding=5
+#menu_y_padding=2
+
+# yes requires the MediaInfo CLI app being installed
+use_mediainfo=yes
+
+#stream_quality_options=2160,1440,1080,720,480
+#aspect_ratios=4:3,16:9,2.35:1,1.36,1.82,0,-1
diff --git a/ar/.config/mpv/script-opts/mdmenu.conf b/ar/.config/mpv/script-opts/mdmenu.conf
new file mode 100644
index 0000000..b8c00ac
--- /dev/null
+++ b/ar/.config/mpv/script-opts/mdmenu.conf
@@ -0,0 +1,13 @@
+# if enabled, dmenu will be embedded inside the mpv instance.
+# on older mpv versions (v0.35.0 and below) depends on
+# [xdo](https://github.com/baskerville/xdo) to get mpv's xwindow id.
+embed=yes
+
+# if enabled, the "current item" (e.g current chapter) will be preselected in dmenu.
+# requires [preselect](https://tools.suckless.org/dmenu/patches/preselect/)
+preselect=no
+
+# command that gets invoked.
+# can be replaced with anything else that's "dmenu compliant" (such as rofi's dmenu mode).
+# arguments are comma separated.
+cmd=dmenu,-i,-l,16
diff --git a/ar/.config/mpv/script-opts/playlist_view.conf b/ar/.config/mpv/script-opts/playlist_view.conf
new file mode 100644
index 0000000..41efdd4
--- /dev/null
+++ b/ar/.config/mpv/script-opts/playlist_view.conf
@@ -0,0 +1,123 @@
+# mpv-gallery-view | https://github.com/occivink/mpv-gallery-view
+# This is the settings file for scripts/playlist-view.lua
+# File placement: script-opts/playlist_view.conf
+# Defaults: https://github.com/occivink/mpv-gallery-view/blob/master/script-opts/playlist_view.conf
+
+# thumbnail directory in which to create and look for thumbnails
+# on Unix-like platforms:
+#thumbs_dir=~/.cache/thumbnails/mpv-gallery
+# on Windows:
+#thumbs_dir=%APPDATA%\mpv\gallery-thumbs-dir
+# note that not all env vars get expanded, only '~' and 'APPDATA' do
+
+# create thumbs_dir if it doesn't exist
+# mkdir_thumbs=yes
+
+# use mpv instead of ffmpeg for thumbnail generation
+# slightly slower and does not support transparency, but does not require additional ffmpeg/ffprobe executables
+# yes on Windows, no on other plateforms
+#generate_thumbnails_with_mpv=no
+
+# all options below are platform-independent
+
+# fine-grained controls for the geometry of the gallery
+# each option can have a fixed value, or dynamic by using the following variables:
+# ww, wh: mpv window width, mpv window height (always available)
+# gx, gy: gallery horizontal position, gallery vertical position
+# gw, gh: gallery width, gallery height
+# sw, sh: minimum spacing width, minimum spacing height
+# tw, th: thumbnail width, thumbnail height
+# these strings are interpreted using the lua equivalent of "eval" so math functions and logical conditions can be used
+# if an option references variables, they will be computed in the appropriate order
+# (for example, if gallery_width == 5 * thumbnail_width, thumbnail_size will be computed before gallery_size)
+# in case of cyclical dependencies, the script will abort
+# example
+# -------
+# make the gallery centered
+gallery_position={ (ww - gw) / 2, (wh - gh) / 2 }
+# make the gallery's size 9/10 the size of the window
+gallery_size={ 9 * ww / 10, 9 * wh / 10 }
+# with at least 15 pixels of spacing between each thumbnail
+min_spacing={ 15, 15 }
+# and two thumbnail size presets for Windows smaller/bigger than 1366 x 768
+thumbnail_size=(ww * wh <= 1366 * 768) and {192, 108} or {288, 162}
+# it is recommended to use discrete increments for thumbnail_size since a new thumbnail needs to be generated for each size
+
+# limit the number of thumbnails visible, even if more could be shown
+# 64 is the maximum due to limitations in mpv
+max_thumbnails=64
+
+# the position in the file at which to take the thumbnail
+# can either be a percentage of the video duration, or a number of seconds
+take_thumbnail_at=20%
+
+# load to the selected video when the playlist-view is toggled off
+load_file_on_toggle_off=no
+# close the playlist-view when loading a video
+close_on_load_file=yes
+# pause the current video when the playlist-view is opened
+pause_on_start=no
+# resume the current video when the playlist-view is closed
+# can be yes, no, or only-if-did-pause
+# in the latter case, will only resume if the video was actually paused by opening the playlist-view
+resume_on_stop=only-if-did-pause
+# automatically start the playlist-view when mpv is started
+start_on_mpv_startup=no
+# automatically start the playlist-view when the current file is finished
+# only has an effect when keep-open=always
+start_on_file_end=no
+# if the currently playing file changes, set the selection to the new one
+follow_playlist_position=no
+# when loading a file, remember the time-position of the previous
+# and restart from there if it's loaded again
+remember_time_position=yes
+
+# show the filename below each thumbnail
+show_text=yes
+# use the playlist title if it exists instead of the filename
+show_title=yes
+strip_directory=yes
+strip_extension=yes
+text_size=28
+
+# colors are defined in hexadecimal in Blue Green Red (BGR) order
+# if multiple colors should be active, they get evenly blended
+# opacity is defined between 00 (opaque) and FF (transparent)
+background_color=333333
+background_opacity=33
+normal_border_color=BBBBBB
+normal_border_size=1
+selected_border_color=E5E4E5
+selected_border_size=6
+# show a special border around the currently playing file
+highlight_active=yes
+active_border_color=EBC5A7
+active_border_size=4
+flagged_border_color=96B58D
+flagged_border_size=4
+placeholder_color=222222
+
+# arbitrary commands that are run when the playlist-view is opened/closed
+# this can be used for lowering video settings when the gallery is active, since
+# high-quality video settings can result in slowdown of the gallery
+command_on_open=
+command_on_close=
+
+# the path of the 'flags' file that is written when you exit mpv
+flagged_file_path=./mpv_gallery_flagged
+
+mouse_support=yes
+UP=k
+DOWN=j
+LEFT=h
+RIGHT=l
+PAGE_UP=ctrl-u
+PAGE_DOWN=ctrl-d
+FIRST=0
+LAST=$
+RANDOM=r
+ACCEPT=ENTER
+CANCEL=ESC
+# this only removes entries from the playlist, not the underlying file
+REMOVE=DEL
+FLAG=SPACE
diff --git a/ar/.config/mpv/script-opts/thumbfast.conf b/ar/.config/mpv/script-opts/thumbfast.conf
new file mode 100644
index 0000000..f0472e0
--- /dev/null
+++ b/ar/.config/mpv/script-opts/thumbfast.conf
@@ -0,0 +1,41 @@
+# Socket path (leave empty for auto)
+socket=
+
+# Thumbnail path (leave empty for auto)
+thumbnail=
+
+# Maximum thumbnail generation size in pixels (scaled down to fit)
+# Values are scaled when hidpi is enabled
+max_height=200
+max_width=200
+
+# Scale factor for thumbnail display size (requires mpv 0.38+)
+# Note that this is lower quality than increasing max_height and max_width
+scale_factor=1
+
+# Apply tone-mapping, no to disable
+tone_mapping=auto
+
+# Overlay id
+overlay_id=42
+
+# Spawn thumbnailer on file load for faster initial thumbnails
+spawn_first=no
+
+# Close thumbnailer process after an inactivity period in seconds, 0 to disable
+quit_after_inactivity=0
+
+# Enable on network playback
+network=no
+
+# Enable on audio playback
+audio=no
+
+# Enable hardware decoding
+hwdec=no
+
+# Windows only: use native Windows API to write to pipe (requires LuaJIT)
+direct_io=no
+
+# Custom path to the mpv executable
+mpv_path=mpv
diff --git a/ar/.config/mpv/scripts/Rename.lua b/ar/.config/mpv/scripts/Rename.lua
new file mode 100644
index 0000000..381eea7
--- /dev/null
+++ b/ar/.config/mpv/scripts/Rename.lua
@@ -0,0 +1,99 @@
+-- Author: Kayizoku - https://github.com/Kayizoku/mpv-rename/edit/main/Rename.lua
+local mp = require("mp")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+
+-- Variable to store the previous filename
+local previousFilename = nil
+
+package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
+local input = require("user-input-module")
+
+local function rename(text, error)
+ if not text then
+ return msg.warn(error)
+ end
+
+ local filepath = mp.get_property("path")
+ if filepath == nil then
+ return
+ end
+
+ local directory, filename = utils.split_path(filepath)
+ local name, extension = filename:match("(%a*)%.([^%./]+)$")
+ if directory == "." then
+ directory = ""
+ end
+ local newfilepath = directory .. text
+
+ -- Store the previous filename before renaming
+ previousFilename = filename
+
+ msg.info(string.format("renaming '%s.%s' to '%s'", name, extension, text))
+ local success, error = os.rename(filepath, newfilepath)
+ if not success then
+ msg.error(error)
+ mp.osd_message("rename failed")
+ return
+ end
+
+ -- adding the new path to the playlist, and restarting the file with the correct path
+ mp.commandv("loadfile", newfilepath, "append")
+ mp.commandv(
+ "playlist-move",
+ mp.get_property_number("playlist-count", 2) - 1,
+ mp.get_property_number("playlist-pos", 1) + 1
+ )
+ mp.commandv("playlist-remove", "current")
+end
+
+-- Function to undo the last rename operation
+local function undoRename()
+ if previousFilename then
+ local filepath = mp.get_property("path")
+ local directory, _ = utils.split_path(filepath)
+ local previousFilepath = directory .. previousFilename
+
+ local success, error = os.rename(filepath, previousFilepath)
+ if success then
+ msg.info("Undo rename successful")
+ mp.osd_message("Undo rename successful")
+ -- Update the playlist with the previous filepath
+ mp.commandv("loadfile", previousFilepath, "append")
+ mp.commandv(
+ "playlist-move",
+ mp.get_property_number("playlist-count", 2) - 1,
+ mp.get_property_number("playlist-pos", 1) + 1
+ )
+ mp.commandv("playlist-remove", "current")
+ -- Clear the previous filename variable
+ previousFilename = nil
+ else
+ msg.error("Failed to undo rename: " .. error)
+ mp.osd_message("Failed to undo rename")
+ end
+ else
+ msg.warn("No previous rename operation to undo")
+ mp.osd_message("No previous rename operation to undo")
+ end
+end
+
+-- Bind a key to the undoRename function
+mp.add_key_binding("U", "undo-rename", undoRename)
+
+-- Registering the event to cancel renaming if the file closes while renaming
+mp.register_event("end-file", function()
+ input.cancel_user_input()
+end)
+
+mp.add_key_binding("F2", "rename-file", function()
+ filepath = mp.get_property("path")
+ directory, filename = utils.split_path(filepath)
+ input.cancel_user_input()
+ input.get_user_input(rename, {
+ text = "Enter new filename:",
+ default_input = filename,
+ replace = false,
+ cursor_pos = filename:find("%.%w+$"),
+ })
+end)
diff --git a/ar/.config/mpv/scripts/SimpleBookmark.lua b/ar/.config/mpv/scripts/SimpleBookmark.lua
new file mode 100644
index 0000000..5af7f74
--- /dev/null
+++ b/ar/.config/mpv/scripts/SimpleBookmark.lua
@@ -0,0 +1,2907 @@
+-- Copyright (c) 2023, Eisa AlAwadhi
+-- License: BSD 2-Clause License
+-- Creator: Eisa AlAwadhi
+-- Project: SimpleBookmark
+-- Version: 1.3.1
+
+local o = {
+---------------------------USER CUSTOMIZATION SETTINGS---------------------------
+--These settings are for users to manually change some options.
+--Changes are recommended to be made in the script-opts directory.
+
+ -----Script Settings----
+ --Available filters: 'all', 'keybinds', 'groups', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
+ --Filters description: "all" to display all the items. Or 'groups' to display the list filtered with items added to any group. Or 'keybinds' to display the list filtered with keybind slots. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file.
+ --Filters can also be stacked by using %+% or omitted by using %-%. e.g.: "groups%+%keybinds" shows only groups and keybinds, "all%-%groups%-%keybinds" shows all items without groups and without keybinds.
+ --Also defined groups can be called by using /:group%Group Name%
+ auto_run_list_idle = 'none', --Auto run the list when opening mpv and there is no video / file loaded. none for disabled. Or choose between available filters.
+ load_item_on_startup = 0, --runs a saved entry when mpv starts based on its number. -1 for oldest entry, 1 for latest entry, or select the number to load a specific entry, 0 for disabled
+ toggle_idlescreen = true, --hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off)
+ resume_offset = -0.65, --change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point
+ osd_messages = true, --true is for displaying osd messages when actions occur. Change to false will disable all osd messages generated from this script
+ bookmark_loads_last_idle = true, --When attempting to bookmark, if there is no video / file loaded, it will instead jump to your last bookmarked item and resume it.
+ bookmark_fileonly_loads_last_idle = true, --When attempting to bookmark fileonly, if there is no video / file loaded, it will instead jump to your last bookmarked item without resuming.
+ mark_bookmark_as_chapter = false, --true is for marking the time as a chapter. false disables mark as chapter behavior.
+ preserve_video_settings = false, --(true/false). Preserve video settings when bookmarking items and loading bookmarks by writing mpv watch-later config
+ bookmark_save_keybind=[[
+ ["ctrl+b", "ctrl+B"]
+ ]], --Keybind that will be used to save the video and its time to log file
+ bookmark_fileonly_keybind=[[
+ ["alt+b", "alt+B"]
+ ]], --Keybind that will be used to save the video without time to log file
+ open_list_keybind=[[
+ [ ["b", "all"], ["B", "all"], ["k", "keybinds"], ["K", "keybinds"] ]
+ ]], --Keybind that will be used to open the list along with the specified filter.
+ list_filter_jump_keybind=[[
+ [ ["b", "all"], ["B", "all"], ["k", "keybinds"], ["K", "keybinds"], ["!", "/:group%TV Shows%"], ["@", "/:group%Movies%"], ["SHARP", "/:group%Anime%"], ["$", "/:group%Anime Movies%"], ["%", "/:group%Cartoon%"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ]
+ ]], --Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available fitlers: 'all', 'keybinds', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
+
+ -----Keybind Slots Settings-----
+ keybinds_quicksave_fileonly = true, --When quick saving to a keybind slot, it will not save position
+ keybinds_empty_auto_create = false, --If the keybind slot is empty, this enables quick logging and adding to slot, Otherwise keybinds are assigned from the list or via quicksave.
+ keybinds_empty_fileonly = true, --When auto creating keybind slot, it will not save position.
+ keybinds_auto_resume = true, --When loading a keybind slot, it will auto resume to the saved time.
+ keybinds_add_load_keybind=[[
+ ["alt+1", "alt+2", "alt+3", "alt+4", "alt+5", "alt+6", "alt+7", "alt+8", "alt+9"]
+ ]], --Keybind that will be used to bind list item to a key, as well as to load it. e.g.: Press alt+1 on list cursor position to add it, press alt+1 while list is hidden to load item keybinded into alt+1. (A new slot is automatically created for each keybind. e.g: .."alt+9, alt+0". Where alt+0 creates a new 10th slot.)
+ keybinds_quicksave_keybind=[[
+ ["alt+!", "alt+@", "alt+#", "alt+$", "alt+%", "alt+^", "alt+&", "alt+*", "alt+("]
+ ]], --To save keybind to a slot without opening the list, to load these keybinds it uses keybinds_add_load_keybind
+ keybinds_remove_keybind=[[
+ ["alt+-"]
+ ]], --Keybind that is used when list is open to remove the keybind slot based on cursor position
+ keybinds_remove_highlighted_keybind=[[
+ ["alt+_"]
+ ]], --Keybind that is used when list is open to remove the keybind slot based on highlighted items
+
+ -----Group Settings-----
+ groups_list_and_keybind =[[
+ [ ["TV Shows", "ctrl+1", "ctrl+!"], ["Movies", "ctrl+2", "ctrl+@"], ["Anime", "ctrl+3", "ctrl+#"], ["Anime Movies", "ctrl+4", "ctrl+$"], ["Cartoon", "ctrl+5"], ["Animated Movies"] ]
+ ]], --Define the groups that can be assigned to a bookmarked item, you can also optionally assign the keybind, and the highlight keybind that puts the bookmarked item into the relevant group when the list is open. Alternatively you can use list_group_add_cycle_keybind to assign item to a group
+ list_groups_remove_keybind=[[
+ ["ctrl+-"]
+ ]], --Keybind that is used when list is open to remove the group based on cursor position
+ list_groups_remove_highlighted_keybind=[[
+ ["ctrl+_"]
+ ]], --Keybind that is used when list is open to remove the group based on highlighted items
+ list_group_add_cycle_keybind=[[
+ ["ctrl+g"]
+ ]], --Keybind to add an item to the group, this cycles through all the different available groups when list is open
+ list_group_add_cycle_highlighted_keybind=[[
+ ["ctrl+G"]
+ ]], --Keybind to add highlighted items to the group, this cycles through all the different available groups when list is open
+
+ -----Logging Settings-----
+ log_path = '/:dir%mpvconf%', --Change to '/:dir%script%' for placing it in the same directory of script, OR change to '/:dir%mpvconf%' for mpv portable_config directory. OR write any variable using '/:var' then the variable '/:var%APPDATA%' you can use path also, such as: '/:var%APPDATA%\\mpv' OR '/:var%HOME%/mpv' OR specify the absolute path , e.g.: 'C:\\Users\\Eisa01\\Desktop\\'
+ log_file = 'mpvBookmark.log', --name+extension of the file that will be used to store the log data
+ file_title_logging = 'all', --Change between 'all', 'protocols', 'local', 'none'. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone
+ logging_protocols=[[
+ ["https?://", "magnet:", "rtmp:"]
+ ]], --add above (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = 'protocols' or file_title_logging = 'all')
+ same_entry_limit = -1, --Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry.
+ overwrite_preserve_properties = true, --true is to preserve groups / slots or any other property when an entry is overwritten.
+
+ -----List Settings-----
+ loop_through_list = false, --true is for going up on the first item loops towards the last item and vise-versa. false disables this behavior.
+ list_middle_loader = true, --false is for more items to show, then u must reach the end. true is for new items to show after reaching the middle of list.
+ quickselect_0to9_keybind = false, --Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work)
+ main_list_keybind_twice_exits = true, --Will exit the list when double tapping the main list, even if the list was accessed through a different filter.
+ search_not_typing_smartly = true, --To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter.
+ search_behavior = 'any', --'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results).
+
+ -----Filter Settings------
+ filters_and_sequence=[[
+ ["all", "keybinds", "groups", "/:group%TV Shows%", "/:group%Movies%", "/:group%Anime%", "/:group%Anime Movies%", "/:group%Cartoon%", "/:group%Animated Movies%", "protocols", "fileonly", "titleonly", "timeonly", "playing", "keywords", "recents", "distinct", "keybinds%+%groups", "all%-%groups%-%keybinds"]
+ ]], --Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed.
+ next_filter_sequence_keybind=[[
+ ["RIGHT", "MBTN_FORWARD"]
+ ]],--Keybind that will be used to go to the next available filter based on the filters_and_sequence
+ previous_filter_sequence_keybind=[[
+ ["LEFT", "MBTN_BACK"]
+ ]],--Keybind that will be used to go to the previous available filter based on the filters_and_sequence
+ loop_through_filters = true, --true is for bypassing the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa. false disables this behavior.
+ keywords_filter_list=[[
+ []
+ ]], --Create a filter out of your desired 'keywords', e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]. To disable this filter keep it empty []
+
+ -----Sort Settings------
+ --Available sorts: 'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'
+ --Sorts description: 'added-asc' is for the newest added item to show first. Or 'added-desc' for the newest added to show last. Or 'alphanum-asc' is for A to Z approach with filename and episode number lower first. Or 'alphanum-desc' is for its Z to A approach. Or 'time-asc', 'time-desc' to sort the list based on time.
+ list_default_sort = 'added-asc', --the default sorting method for all the different filters in the list. Choose between available sorts.
+ list_filters_sort=[[
+ [ ["keybinds", "keybind-asc"], ["fileonly", "alphanum-asc"], ["playing", "time-asc"] ]
+ ]], --Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ]
+ list_cycle_sort_keybind=[[
+ ["alt+s", "alt+S"]
+ ]], --Keybind to cycle through the different available sorts when list is open
+
+ -----List Design Settings-----
+ list_alignment = 7, --The alignment for the list, uses numpad positions choose from 1-9 or 0 to disable. e,g.:7 top left alignment, 8 top middle alignment, 9 top right alignment.
+ slice_name = false, --Change to true or false. Slices long names per the amount specified below
+ slice_name_amount = 55, --Amount for slicing long names (for path, name, and title) list_content_text variables
+ list_show_amount = 10, --Change maximum number to show items at once
+ list_content_text = '%number%. %name%%0_duration%%duration%%0_keybind%%keybind%%0_group%%group%%1_group%\\h\\N\\N', --Text to be shown as header for the list
+ --list_content_text variables: %quickselect%, %number%, %name%, %title%, %path%, %duration%, %length%, %remaining%, %dt%, %dt_"format%"%
+ --Variables explanation: %quickselect%: keybind for quickselect. %number%: numbered sequence of the item position. %name%: shows the file name. %title%: shows file title. %path%: shows the filepath or url. %duration%: the reached playback time of item. %length%: the total time length of the file. %remaining% the remaining playback time of file. %dt%: the logged date and time.
+ --You can also use %dt_"format%"%" as per lua date formatting (https://www.lua.org/pil/22.1.html). It is specified after dt_ ..example: (%dt_%a% %dt_%b% %dt_%y%) for abbreviated day month year
+ list_content_variables=[[
+ [ ["0_duration", " 🕒 "], ["0_keybind", " ⌨ "], ["0_group", " 🖿 "] ]
+ ]], --User defined variables that only displays if the related variable is triggered.
+ --#_group, #_keybind, #_duration, #_length, #_remaining, #_dt. (# represents the possibility of creating many variables using different numbers. e.g.: "0_keybind", "1_keybind")
+ list_sliced_prefix = '...\\h\\N\\N', --The text that indicates there are more items above. \\N is for new line. \\h is for hard space.
+ list_sliced_suffix = '...', --The text that indicates there are more items below.
+ text_color = 'ffffff', --Text color for list in BGR hexadecimal
+ text_scale = 50, --Font size for the text of list
+ text_border = 0.7, --Black border size for the text of list
+ text_cursor_color = 'ffbf7f', --Text color of current cursor position in BGR hexadecimal
+ text_cursor_scale = 50, --Font size for text of current cursor position in list
+ text_cursor_border = 0.7, --Black border size for text of current cursor position in list
+ text_highlight_pre_text = '✅ ', --Pre text for highlighted multi-select item
+ search_color_typing = '00bfff', --Search color when in typing mode
+ search_color_not_typing = 'ffffaa', --Search color when not in typing mode and it is active
+ header_color = 'ffffaa', --Header color in BGR hexadecimal
+ header_scale = 55, --Header text size for the list
+ header_border = 0.8, --Black border size for the Header of list
+ header_text = '🔖 Bookmarks [%cursor%/%total%]%0_highlight%%highlight%%0_filter%%filter%%1_filter%%0_sort%%sort%%1_sort%%0_search%%search%%1_search%\\h\\N\\N', --The formatting of the items when you open the list
+ --header_text variables: %cursor%, %total%, %highlight%, %filter%, %search%, %duration%, %length%, %remaining%.
+ --Variables explanation: %cursor%: the number of cursor position. %total%: total amount in current list. %highlight%: total number of highlighted items. %filter%: shows the filter name, %search%: shows the typed search. %duration%: the total reached playback time of all displayed items. %length%: the total time length of the file for all displayed items. %remaining% the remaining playback time of file for all the displayed items.
+ header_variables=[[
+ [ ["0_highlight", "✅"], ["0_filter", " [Filter: "], ["1_filter", "]"], ["0_sort", " \\{"], ["1_sort", "}"], ["0_search", "\\h\\N\\N[Search="], ["1_search", "..]"] ]
+ ]], --User defined variables that only displays if the related variable is triggered.
+ --#_filter, #_sort, #_highlight, #_search, #_duration, #_length%, #_remaining. (# represents the possibility of creating many variables using different numbers. e.g.: "0_filter", "1_filter")
+ header_sort_hide_text = 'added-asc',--Sort method that is hidden from header when using %sort% variable
+
+ -----Time Format Settings-----
+ --in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability).
+ --in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds.
+ --in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5]
+ osd_time_format=[[
+ ["default", "truncate"]
+ ]],
+ list_duration_time_format=[[
+ ["default", "truncate"]
+ ]],
+ list_length_time_format=[[
+ ["default", "truncate"]
+ ]],
+ list_remaining_time_format=[[
+ ["default", "truncate"]
+ ]],
+ header_duration_time_format=[[
+ ["hms", "truncate", ":"]
+ ]],
+ header_length_time_format=[[
+ ["hms", "truncate", ":"]
+ ]],
+ header_remaining_time_format=[[
+ ["hms", "truncate", ":"]
+ ]],
+
+ -----List Keybind Settings-----
+ --Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind
+ --Example of changing and adding keybinds: --From ["b", "B"] To ["b"]. --From [""] to ["alt+b"]. --From [""] to ["a" "ctrl+a", "alt+a"]
+ list_move_up_keybind=[[
+ ["UP", "WHEEL_UP"]
+ ]], --Keybind that will be used to navigate up on the list
+ list_move_down_keybind=[[
+ ["DOWN", "WHEEL_DOWN"]
+ ]], --Keybind that will be used to navigate down on the list
+ list_page_up_keybind=[[
+ ["PGUP"]
+ ]], --Keybind that will be used to go to the first item for the page shown on the list
+ list_page_down_keybind=[[
+ ["PGDWN"]
+ ]], --Keybind that will be used to go to the last item for the page shown on the list
+ list_move_first_keybind=[[
+ ["HOME"]
+ ]], --Keybind that will be used to navigate to the first item on the list
+ list_move_last_keybind=[[
+ ["END"]
+ ]], --Keybind that will be used to navigate to the last item on the list
+ list_highlight_move_keybind=[[
+ ["SHIFT"]
+ ]], --Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc..
+ list_highlight_all_keybind=[[
+ ["ctrl+a", "ctrl+A"]
+ ]], --Keybind that will be used to highlight all displayed items on the list
+ list_unhighlight_all_keybind=[[
+ ["ctrl+d", "ctrl+D"]
+ ]], --Keybind that will be used to remove all currently highlighted items from the list
+ list_select_keybind=[[
+ ["ENTER", "MBTN_MID"]
+ ]], --Keybind that will be used to load entry based on cursor position
+ list_add_playlist_keybind=[[
+ ["CTRL+ENTER"]
+ ]], --Keybind that will be used to add entry to playlist based on cursor position
+ list_add_playlist_highlighted_keybind=[[
+ ["SHIFT+ENTER"]
+ ]], --Keybind that will be used to add all highlighted entries to playlist
+ list_close_keybind=[[
+ ["ESC", "MBTN_RIGHT"]
+ ]], --Keybind that will be used to close the list (closes search first if it is open)
+ list_delete_keybind=[[
+ ["DEL"]
+ ]], --Keybind that will be used to delete the entry based on cursor position
+ list_delete_highlighted_keybind=[[
+ ["SHIFT+DEL"]
+ ]], --Keybind that will be used to delete all highlighted entries from the list
+ list_search_activate_keybind=[[
+ ["ctrl+f", "ctrl+F"]
+ ]], --Keybind that will be used to trigger search
+ list_search_not_typing_mode_keybind=[[
+ ["ALT+ENTER"]
+ ]], --Keybind that will be used to exit typing mode of search while keeping search open
+ list_ignored_keybind=[[
+ ["h", "H", "r", "R", "c", "C"]
+ ]], --Keybind thats are ignored when list is open
+
+---------------------------END OF USER CUSTOMIZATION SETTINGS---------------------------
+}
+
+(require 'mp.options').read_options(o)
+local utils = require 'mp.utils'
+local msg = require 'mp.msg'
+
+o.filters_and_sequence = utils.parse_json(o.filters_and_sequence)
+o.keywords_filter_list = utils.parse_json(o.keywords_filter_list)
+o.list_filters_sort = utils.parse_json(o.list_filters_sort)
+o.logging_protocols = utils.parse_json(o.logging_protocols)
+o.osd_time_format = utils.parse_json(o.osd_time_format)
+o.list_duration_time_format = utils.parse_json(o.list_duration_time_format)--1.3# changed and added time format for each in the list
+o.list_length_time_format = utils.parse_json(o.list_length_time_format)--1.3# added time format for each in the list
+o.list_remaining_time_format = utils.parse_json(o.list_remaining_time_format)--1.3# added time format for each in the list
+o.header_duration_time_format = utils.parse_json(o.header_duration_time_format)
+o.header_length_time_format = utils.parse_json(o.header_length_time_format)
+o.header_remaining_time_format = utils.parse_json(o.header_remaining_time_format)
+o.bookmark_save_keybind = utils.parse_json(o.bookmark_save_keybind)
+o.bookmark_fileonly_keybind = utils.parse_json(o.bookmark_fileonly_keybind)
+o.keybinds_add_load_keybind = utils.parse_json(o.keybinds_add_load_keybind)
+o.keybinds_remove_keybind = utils.parse_json(o.keybinds_remove_keybind)
+o.keybinds_remove_highlighted_keybind = utils.parse_json(o.keybinds_remove_highlighted_keybind)
+o.keybinds_quicksave_keybind = utils.parse_json(o.keybinds_quicksave_keybind)
+o.groups_list_and_keybind = utils.parse_json(o.groups_list_and_keybind)
+o.list_groups_remove_keybind = utils.parse_json(o.list_groups_remove_keybind)
+o.list_groups_remove_highlighted_keybind = utils.parse_json(o.list_groups_remove_highlighted_keybind)
+o.list_group_add_cycle_keybind = utils.parse_json(o.list_group_add_cycle_keybind)
+o.list_group_add_cycle_highlighted_keybind = utils.parse_json(o.list_group_add_cycle_highlighted_keybind)
+o.list_move_up_keybind = utils.parse_json(o.list_move_up_keybind)
+o.list_move_down_keybind = utils.parse_json(o.list_move_down_keybind)
+o.list_page_up_keybind = utils.parse_json(o.list_page_up_keybind)
+o.list_page_down_keybind = utils.parse_json(o.list_page_down_keybind)
+o.list_move_first_keybind = utils.parse_json(o.list_move_first_keybind)
+o.list_move_last_keybind = utils.parse_json(o.list_move_last_keybind)
+o.list_highlight_move_keybind = utils.parse_json(o.list_highlight_move_keybind)
+o.list_highlight_all_keybind = utils.parse_json(o.list_highlight_all_keybind)
+o.list_unhighlight_all_keybind = utils.parse_json(o.list_unhighlight_all_keybind)
+o.list_cycle_sort_keybind = utils.parse_json(o.list_cycle_sort_keybind)
+o.list_content_variables = utils.parse_json(o.list_content_variables)--1.3# for new config
+o.header_variables = utils.parse_json(o.header_variables)--1.3# for new config
+o.list_select_keybind = utils.parse_json(o.list_select_keybind)
+o.list_add_playlist_keybind = utils.parse_json(o.list_add_playlist_keybind)
+o.list_add_playlist_highlighted_keybind = utils.parse_json(o.list_add_playlist_highlighted_keybind)
+o.list_close_keybind = utils.parse_json(o.list_close_keybind)
+o.list_delete_keybind = utils.parse_json(o.list_delete_keybind)
+o.list_delete_highlighted_keybind = utils.parse_json(o.list_delete_highlighted_keybind)
+o.list_search_activate_keybind = utils.parse_json(o.list_search_activate_keybind)
+o.list_search_not_typing_mode_keybind = utils.parse_json(o.list_search_not_typing_mode_keybind)
+o.next_filter_sequence_keybind = utils.parse_json(o.next_filter_sequence_keybind)
+o.previous_filter_sequence_keybind = utils.parse_json(o.previous_filter_sequence_keybind)
+o.open_list_keybind = utils.parse_json(o.open_list_keybind)
+o.list_filter_jump_keybind = utils.parse_json(o.list_filter_jump_keybind)
+o.list_ignored_keybind = utils.parse_json(o.list_ignored_keybind)
+
+if utils.shared_script_property_set then
+ utils.shared_script_property_set('simplebookmark-menu-open', 'no')
+end
+mp.set_property('user-data/simplebookmark/menu-open', 'no')
+
+if string.lower(o.log_path) == '/:dir%mpvconf%' then
+ o.log_path = mp.find_config_file('.')
+elseif string.lower(o.log_path) == '/:dir%script%' then
+ o.log_path = debug.getinfo(1).source:match('@?(.*/)')
+elseif o.log_path:match('/:var%%(.*)%%') then
+ local os_variable = o.log_path:match('/:var%%(.*)%%')
+ o.log_path = o.log_path:gsub('/:var%%(.*)%%', os.getenv(os_variable))
+end
+local log_fullpath = utils.join_path(o.log_path, o.log_file)
+
+local log_length_text = 'length='
+local log_time_text = 'time='
+local log_keybind_text = 'slot='
+local log_group_text = 'group='
+local protocols = {'https?:', 'magnet:', 'rtmps?:', 'smb:', 'ftps?:', 'sftp:'}
+local available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'}
+local search_string = ''
+local search_active = false
+local loadTriggered = false --1.3.0# to identify if load is triggered atleast once for idle option
+local resume_selected = false
+local osd_log_contents = {}
+local list_start = 0
+local list_cursor = 1
+local list_highlight_cursor = {}
+local list_drawn = false
+local list_pages = {}
+local filePath, fileTitle, fileLength
+local seekTime = 0
+local filterName = 'all'
+local sortName
+
+function starts_protocol(tab, val)
+ for index, value in ipairs(tab) do
+ if (val:find(value) == 1) then
+ return true
+ end
+ end
+ return false
+end
+
+function contain_value(tab, val)
+ if not tab then return msg.error('check value passed') end
+ if not val then return msg.error('check value passed') end
+
+ for index, value in ipairs(tab) do
+ if value.match(string.lower(val), string.lower(value)) then
+ return true
+ end
+ end
+
+ return false
+end
+
+function has_value(tab, val, array2d)
+ if not tab then return msg.error('check value passed') end
+ if not val then return msg.error('check value passed') end
+ if not array2d then
+ for index, value in ipairs(tab) do
+ if string.lower(value) == string.lower(val) then
+ return true
+ end
+ end
+ end
+ if array2d then
+ for i=1, #tab do
+ if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then
+ return true
+ end
+ end
+ end
+
+ return false
+end
+
+function file_exists(name)
+ local f = io.open(name, "r")
+ if f ~= nil then io.close(f) return true else return false end
+end
+
+function format_time(seconds, sep, decimals, style)
+ local function divmod (a, b)
+ return math.floor(a / b), a % b
+ end
+ decimals = decimals == nil and 3 or decimals
+
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ if decimals == 'truncate' then
+ s = math.floor(s)
+ decimals = 0
+ if style == 'timestamp' then
+ seconds = math.floor(seconds)
+ end
+ end
+
+ if not style or style == '' or style == 'default' then
+ local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals)
+ sep = sep and sep or ":"
+ return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s)
+ elseif style == 'hms' or style == 'hms-full' then
+ sep = sep ~= nil and sep or " "
+ if style == 'hms-full' or h > 0 then
+ return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s)
+ elseif m > 0 then
+ return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s)
+ else
+ return string.format("%." .. tostring(decimals) .. "fs", s)
+ end
+ elseif style == 'timestamp' then
+ return string.format("%." .. tostring(decimals) .. "f", seconds)
+ elseif style == 'timestamp-concise' then
+ return seconds
+ end
+end
+
+function get_file() --1.3# removed prefer filename overtitle
+ local path = mp.get_property('path')
+ if not path then return end
+
+ local length = (mp.get_property_number('duration') or 0)
+
+ local title = mp.get_property('media-title'):gsub("\"", "")
+
+ return path, title, length
+end
+
+function get_local_names(target, property) --1.3# function to get names and fall back to whatever is found --1.2.4# removed or "" so that I can check for errors if the returned value is nil
+ local target_filename = target.found_name or target.found_title or target.found_path
+ local target_filepath = target.found_path or target.found_name or target.found_title
+ local target_filetitle = target.found_title or target.found_name or target.found_path
+ if not property then
+ return target_filename, target_filepath, target_filetitle
+ elseif property == 'osd' then --1.3# added osd property so it removes special character functions when displaying osd in mpv (uses gsub from search)
+ return esc_ass(target_filename), esc_ass(target_filepath), esc_ass(target_filetitle)
+ end
+end
+
+function get_slot_keybind(keyindex)
+ local keybind_return
+
+ if o.keybinds_add_load_keybind[keyindex] then
+ keybind_return = o.keybinds_add_load_keybind[keyindex]
+ else
+ keybind_return = log_keybind_text .. (keyindex or '') .. ' undefined'
+ end
+
+ return keybind_return
+end
+
+function get_group_properties(groupindex, action)
+ local gname, gkeybind, ghkeybind
+
+ if o.groups_list_and_keybind[groupindex] and o.groups_list_and_keybind[groupindex][1] then
+ gname = o.groups_list_and_keybind[groupindex][1]
+ else
+ gname = log_group_text ..(groupindex or '').. ' undefined'
+ end
+
+ if o.groups_list_and_keybind[groupindex] and o.groups_list_and_keybind[groupindex][2] then
+ gkeybind = o.groups_list_and_keybind[groupindex][2]
+ else
+ gkeybind = log_group_text ..(groupindex or '').. ' undefined'
+ end
+
+ if o.groups_list_and_keybind[groupindex] and o.groups_list_and_keybind[groupindex][3] then
+ ghkeybind = o.groups_list_and_keybind[groupindex][3]
+ else
+ ghkeybind = log_group_text ..(groupindex or '').. ' undefined'
+ end
+
+ return {name = gname, keybind = gkeybind, highlight_keybind = ghkeybind}
+end
+
+function bind_keys(keys, name, func, opts)
+ if not keys then
+ mp.add_forced_key_binding(keys, name, func, opts)
+ return
+ end
+
+ for i = 1, #keys do
+ if i == 1 then
+ mp.add_forced_key_binding(keys[i], name, func, opts)
+ else
+ mp.add_forced_key_binding(keys[i], name .. i, func, opts)
+ end
+ end
+end
+
+function unbind_keys(keys, name)
+ if not keys then
+ mp.remove_key_binding(name)
+ return
+ end
+
+ for i = 1, #keys do
+ if i == 1 then
+ mp.remove_key_binding(name)
+ else
+ mp.remove_key_binding(name .. i)
+ end
+ end
+end
+
+function esc_string(str)
+ return str:gsub("([%p])", "%%%1")
+end
+
+function esc_ass(str) --1.3# used function to escape, also this function will use the byte order mark instead of immediately pasting the zero-width space
+ return str:gsub('\\', '\\\239\187\191'):gsub('{', '\\{')
+end
+
+---------Start of LogManager---------
+--LogManager (Read and Format the List from Log)--
+function read_log(func)
+ local f = io.open(log_fullpath, "r")
+ if not f then return end
+ local contents = {}
+ local line_count = 0
+ for line in f:lines() do
+ table.insert(contents, (func(line)))
+ end
+ f:close()
+ return contents
+end
+
+function read_log_table()
+ local line_pos = 0
+ return read_log(function(line)
+ local tt, p, t, s, d, n, e, l, dt, ln, r, g
+ if line:match('^.-\"(.-)\"') then --1.3# changed if statement to only get title and path
+ tt, p = line:match('^.-\"(.-)\" | (.*) | ' .. esc_string(log_length_text) .. '(.*)')
+ else --1.3# get path only if no title is there
+ p = line:match('[(.*)%]]%s(.*) | ' .. esc_string(log_length_text) .. '(.*)')
+ end
+ d, n, e = p:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$') --1.3# not inside if statement anymore since we are not changing name with title anymore
+ dt = line:match('%[(.-)%]')
+ t = line:match(' | ' .. esc_string(log_time_text) .. '(%d*%.?%d*)(.*)$')
+ ln = line:match(' | ' .. esc_string(log_length_text) .. '(%d*%.?%d*)(.*)$')
+ if tonumber(ln) and tonumber(t) then r = tonumber(ln) - tonumber(t) else r = 0 end
+ s = line:match(' | .* | ' .. esc_string(log_keybind_text) .. '(%d*)(.*)$')
+ g = line:match(' | .* | ' .. esc_string(log_group_text) .. '(%d*)(.*)$')
+ l = line
+ line_pos = line_pos + 1
+ return {found_path = p, found_time = t, found_name = n, found_title = tt, found_line = l, found_sequence = line_pos, found_directory = d, found_datetime = dt, found_length = ln, found_remaining = r, found_slot = s, found_group = g}
+ end)
+end
+
+function list_sort(tab, sort)
+ if sort == 'added-asc' then
+ table.sort(tab, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+ elseif sort == 'added-desc' then
+ table.sort(tab, function(a, b) return a['found_sequence'] > b['found_sequence'] end)
+ elseif sort == 'keybind-asc' and filterName == 'keybinds' then
+ table.sort(tab, function(a, b) return a['found_slot'] > b['found_slot'] end)
+ elseif sort == 'keybind-desc' and filterName == 'keybinds' then
+ table.sort(tab, function(a, b) return a['found_slot'] < b['found_slot'] end)
+ elseif sort == 'time-asc' then
+ table.sort(tab, function(a, b) return tonumber(a['found_time']) > tonumber(b['found_time']) end)
+ elseif sort == 'time-desc' then
+ table.sort(tab, function(a, b) return tonumber(a['found_time']) < tonumber(b['found_time']) end)
+ elseif sort == 'alphanum-asc' or sort == 'alphanum-desc' then
+ local function padnum(d) local dec, n = string.match(d, "(%.?)0*(.+)")
+ return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) end
+ if sort == 'alphanum-asc' then
+ table.sort(tab, function(a, b) return tostring(a['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) > tostring(b['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end)
+ elseif sort == 'alphanum-desc' then
+ table.sort(tab, function(a, b) return tostring(a['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) < tostring(b['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end)
+ end
+ end
+
+ return tab
+end
+
+function get_o_variable(str, arr_var) --1.3# function to get variable content from passed array
+ if not str then return end
+ if str:match('%%(.*)%%') then str = str:match('%%(.*)%%') end --1.3# if the entry has % around it, then remove it
+
+ local var_return
+ for i = 1, #arr_var do --1.3# loop through the passed array and get the value of the matched variable
+ if arr_var[i][1] == str then
+ var_return = arr_var[i][2] --1.3# added or "" so if that the content of the variable is not defined it does not crash
+ break
+ end
+ end
+
+ return var_return or "" --1.3# return the founded variable content or empty string if nothing is found
+
+end
+
+function parse_list_item(str, properties) --1.3#add ability to parse the contents of the list like the header
+ if not str then return msg.error('str in parse_list_item is nil') end
+
+ local list_filename, list_filepath, list_filetitle = get_local_names(properties["item"],'osd')--1.3# added osd property so it removes special characters for displaying list
+
+ if o.slice_name and list_filepath:len() > o.slice_name_amount then --1.3.1# fix #86 since p doesn't exist anymore, and checks for specific filename / filepath / filetitle, so slicing is accurate.
+ list_filepath = list_filepath:sub(1, o.slice_name_amount) .. "..."
+ end
+ if o.slice_name and list_filename:len() > o.slice_name_amount then
+ list_filename = list_filename:sub(1, o.slice_name_amount) .. "..."
+ end
+ if o.slice_name and list_filetitle:len() > o.slice_name_amount then
+ list_filetitle = list_filetitle:sub(1, o.slice_name_amount) .. "..."
+ end
+
+ str = str:gsub("%%name%%", list_filename)
+ :gsub("%%path%%", list_filepath)
+ :gsub("%%title%%", list_filetitle)
+ :gsub("%%number%%", properties["index"]+1) --1.3# index +1 is the osd_index
+ :gsub("%%dt%%", properties["item"].found_datetime)
+
+ for s in str:gmatch("%%dt_%%.%%") do --1.3# loop through all found dt_ in the script a
+ local svar = s:match("_%%."):sub(2) --1.3# match whatever starting from _ when using %dt_var%, then sub(2) to remove the first letter which is _ (then var will remain) to use in our gsub
+ if parse_8601(properties["item"].found_datetime) then --1.3.1# for backward compatibility if matching did not work reset to null
+ str = str:gsub(esc_string(s), os.date(svar, parse_8601(properties["item"].found_datetime))) --1.3# replaces the found dt_var with eg. dt_%a from config
+
+ for x in str:gmatch("%%%d_dt%%") do --1.3.1# for backward compatibility adds 0_dt to be able to force customize date and time variables
+ str = str:gsub(esc_string(x), get_o_variable(x, o.list_content_variables))
+ end
+ else --1.3.1# for backward compatibility if matching did not work reset to null
+ str = str:gsub(esc_string(s), "")
+
+ for x in str:gmatch("%%%d_dt%%") do --1.3.1# for backward compatibility removes 0_dt if log time cannot be parsed from in log
+ str = str:gsub(esc_string(x), "")
+ end
+ end
+ end
+
+ if properties["item"].found_slot then
+ str = str:gsub("%%keybind%%", get_slot_keybind(tonumber(properties["item"].found_slot)))
+ for s in str:gmatch("%%%d*_keybind%%") do --1.3# if a custom group variable is found, such as %0_group% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables))
+ end
+ else
+ str = str:gsub("%%keybind%%", "")
+ for s in str:gmatch("%%%d*_keybind%%") do --1.3# if a custom slot is found and there is no slot assigned, remove it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+
+ if properties["item"].found_group then
+ str = str:gsub("%%group%%", get_group_properties(tonumber(properties["item"].found_group)).name)
+ for s in str:gmatch("%%%d*_group%%") do --1.3# if a custom group variable is found, such as %0_group% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables))
+ end
+ else
+ str = str:gsub("%%group%%", "")
+ for s in str:gmatch("%%%d*_group%%") do
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+
+ if properties['quickselect'] and str:match("%%quickselect%%") then --1.2# replace quickselect with the actual key if its available
+ str = str:gsub("%%quickselect%%", properties['quickselect'])
+ else
+ str = str:gsub("%%quickselect%%", "")
+ end
+
+ --1.3# same concept for showing groups but for time
+ if properties["item"].found_time and tonumber(properties["item"].found_time) > 0 then
+ str = str:gsub('%%duration%%', format_time(properties["item"].found_time, o.list_duration_time_format[3], o.list_duration_time_format[2], o.list_duration_time_format[1]))
+ for s in str:gmatch("%%%d*_duration%%") do
+ str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables))
+ end
+ else
+ str = str:gsub("%%duration%%", "")
+ for s in str:gmatch("%%%d*_duration%%") do
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ if properties["item"].found_length and tonumber(properties["item"].found_length) > 0 then
+ str = str:gsub('%%length%%', format_time(properties["item"].found_length, o.list_length_time_format[3], o.list_length_time_format[2], o.list_length_time_format[1]))
+ for s in str:gmatch("%%%d*_length%%") do
+ str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables))
+ end
+ else
+ str = str:gsub("%%length%%", "")
+ for s in str:gmatch("%%%d*_length%%") do
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ if properties["item"].found_remaining and tonumber(properties["item"].found_remaining) > 0 then
+ str = str:gsub('%%remaining%%', format_time(properties["item"].found_remaining, o.list_remaining_time_format[3], o.list_remaining_time_format[2], o.list_remaining_time_format[1]))
+ for s in str:gmatch("%%%d*_remaining%%") do
+ str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables))
+ end
+ else
+ str = str:gsub("%%remaining%%", "")
+ for s in str:gmatch("%%%d*_remaining%%") do
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ str = str:gsub("%%%%", "%%")
+
+ return str
+end
+
+function parse_header(str)
+ local osd_header_color = string.format("{\\1c&H%s}", o.header_color)
+ local osd_search_color = osd_header_color
+ if search_active == 'typing' then
+ osd_search_color = string.format("{\\1c&H%s}", o.search_color_typing)
+ elseif search_active == 'not_typing' then
+ osd_search_color = string.format("{\\1c&H%s}", o.search_color_not_typing)
+ end
+
+ str = str:gsub("%%total%%", #osd_log_contents)
+ :gsub("%%cursor%%", list_cursor)
+
+ local filter_osd = filterName
+ if filter_osd ~= 'all' then
+ if filter_osd:match('/:group%%(.*)%%') then filter_osd = filter_osd:match('/:group%%(.*)%%') end
+ str = str:gsub("%%filter%%", filter_osd)
+ for s in str:gmatch("%%%d*_filter%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%filter%%", '')
+ for s in str:gmatch("%%%d*_filter%%") do --1.3# if a custom slot is found and there is no slot assigned, remove it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+
+ if str:match('%%duration%%') then
+ if get_total_duration('found_time') > 0 then
+ str = str:gsub("%%duration%%", format_time(get_total_duration('found_time'), o.header_duration_time_format[3], o.header_duration_time_format[2], o.header_duration_time_format[1]))
+ for s in str:gmatch("%%%d*_duration%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%duration%%", '')
+ for s in str:gmatch("%%%d*_duration%%") do --1.3# if a custom slot is found and there is no slot assigned, remove it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ end
+
+ if str:match('%%length%%') then
+ if get_total_duration('found_length') > 0 then
+ str = str:gsub("%%length%%", format_time(get_total_duration('found_length'), o.header_length_time_format[3], o.header_length_time_format[2], o.header_length_time_format[1]))
+ for s in str:gmatch("%%%d*_length%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%length%%", '')
+ for s in str:gmatch("%%%d*_length%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ end
+
+ if str:match('%remaining%%') then
+ if get_total_duration('found_remaining') > 0 then
+ str = str:gsub("%%remaining%%", format_time(get_total_duration('found_remaining'), o.header_remaining_time_format[3], o.header_remaining_time_format[2], o.header_remaining_time_format[1]))
+ for s in str:gmatch("%%%d*_remaining%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%remaining%%", '')
+ for s in str:gmatch("%%%d*_remaining%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ end
+
+ if #list_highlight_cursor > 0 then
+ str = str:gsub("%%highlight%%", #list_highlight_cursor)
+ for s in str:gmatch("%%%d*_highlight%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%highlight%%", '')
+ for s in str:gmatch("%%%d*_highlight%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+
+ if sortName and sortName ~= o.header_sort_hide_text then
+ str = str:gsub("%%sort%%", sortName)
+ for s in str:gmatch("%%%d*_sort%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%sort%%", '')
+ for s in str:gmatch("%%%d*_sort%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+
+ if search_active then
+ local search_string_osd = search_string
+ if search_string_osd ~= '' then
+ search_string_osd = esc_ass(search_string:gsub('%%', '%%%%%%%%')) --1.3# used ass_escape instead of gsub
+ end
+
+ str = str:gsub("%%search%%", osd_search_color..search_string_osd..osd_header_color)
+ for s in str:gmatch("%%%d*_search%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables))
+ end
+ else
+ str = str:gsub("%%search%%", '')
+ for s in str:gmatch("%%%d*_search%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it
+ str = str:gsub(esc_string(s), "")
+ end
+ end
+ str = str:gsub("%%%%", "%%")
+ return str
+end
+
+function search_log_contents(arr_contents)
+ if not arr_contents or not arr_contents[1] or not search_active or not search_string == '' then return false end
+
+ local search_query = ''
+ for search in search_string:gmatch("[^%s]+") do
+ search_query = search_query..'.-'..esc_string(search)
+ end
+ local contents_string = ''
+
+ local search_arr_contents = {}
+
+ for i = 1, #arr_contents do --1.3# removed specific search method as it doesn't seem useful anymore, --1.3.1# utilize arr_contents instead of osd_log_contents
+ if o.search_behavior == 'any' then
+ contents_string = arr_contents[i].found_datetime --1.3# seperated date and time for search
+ if parse_8601(arr_contents[i].found_datetime) then --1.3# an if statement to check if date could be parsed --1.3# allows for all type of dates to be searched thanks to the loop
+ local os_date_tag= {'%a', '%A', '%b', '%B', '%c', '%d', '%H', '%I', '%M', '%m', '%p', '%S', '%w', '%x', '%X', '%Y', '%y'} --1.3# add all lua date time parameters
+ for j=1, #os_date_tag do --1.3# replace all lua parameters with date values that can be searched
+ contents_string = contents_string..os.date(os_date_tag[j], parse_8601(arr_contents[i].found_datetime))
+ end
+ end
+ contents_string = contents_string..(arr_contents[i].found_title or '')..(arr_contents[i].found_name or '')..arr_contents[i].found_path --1.3# added found_name since parsing is different now
+ if tonumber(arr_contents[i].found_time) > 0 then
+ contents_string = contents_string..format_time(arr_contents[i].found_time, o.list_duration_time_format[3], o.list_duration_time_format[2], o.list_duration_time_format[1])
+ end
+ if tonumber(arr_contents[i].found_length) > 0 then
+ contents_string = contents_string..format_time(arr_contents[i].found_length, o.list_length_time_format[3], o.list_length_time_format[2], o.list_length_time_format[1])
+ end
+ if tonumber(arr_contents[i].found_remaining) > 0 then
+ contents_string = contents_string..format_time(arr_contents[i].found_remaining, o.list_remaining_time_format[3], o.list_remaining_time_format[2], o.list_remaining_time_format[1])
+ end
+ if arr_contents[i].found_slot then
+ contents_string = contents_string..get_slot_keybind(tonumber(arr_contents[i].found_slot))
+ end
+ if arr_contents[i].found_group then
+ contents_string = contents_string..get_group_properties(tonumber(arr_contents[i].found_group)).name
+ end
+ elseif o.search_behavior == 'any-notime' then
+ contents_string = arr_contents[i].found_datetime --1.3# seperated date and time for search
+ if parse_8601(arr_contents[i].found_datetime) then --1.3# an if statement to check if date could be parsed
+ local os_date_tag= {'%a', '%A', '%b', '%B', '%c', '%d', '%H', '%I', '%M', '%m', '%p', '%S', '%w', '%x', '%X', '%Y', '%y'} --1.3# add all lua date time parameters
+ for j=1, #os_date_tag do --1.3# replace all lua parameters with values that can be searched
+ contents_string = contents_string..os.date(os_date_tag[j], parse_8601(arr_contents[i].found_datetime))
+ end
+ end
+ contents_string = contents_string..(arr_contents[i].found_title or '')..(arr_contents[i].found_name or '')..arr_contents[i].found_path --1.3# added found_name since parsing is different now
+ if arr_contents[i].found_slot then
+ contents_string = contents_string..get_slot_keybind(tonumber(arr_contents[i].found_slot))
+ end
+ if arr_contents[i].found_group then
+ contents_string = contents_string..get_group_properties(tonumber(arr_contents[i].found_group)).name
+ end
+ end
+
+ if string.lower(contents_string):match(string.lower(search_query)) then
+ table.insert(search_arr_contents, arr_contents[i])
+ end
+ end
+
+ return search_arr_contents
+
+end
+
+function filter_log_contents(arr_contents, filter)
+ if not arr_contents or not arr_contents[1] or not filter or filter == 'all' then return false end
+ local filtered_arr_contents = {}
+
+ if filter:match('%%%+%%') then
+ if filter_stack(arr_contents,filter) then filtered_arr_contents = filter_stack(arr_contents, filter) end
+ elseif filter:match('%%%-%%') then
+ if filter_omit(arr_contents,filter) then filtered_arr_contents = filter_omit(arr_contents, filter) end
+ else
+ if filter_apply(arr_contents, filter) then filtered_arr_contents = filter_apply(arr_contents, filter) end
+ end
+
+ return filtered_arr_contents
+end
+
+
+function filter_omit(arr_contents, filter)
+ if not arr_contents or not arr_contents[1] or not filter or filter == 'all' or not filter:match('%%%-%%') then return false end
+ local omitted_arr_table = arr_contents
+
+ local filter_items = {}
+ for f in filter:gmatch("[^%%%-%%\r+]+") do
+ table.insert(filter_items, f)
+ end
+
+ local temp_filtered_contents = arr_contents
+ for i=1, #filter_items do
+ if i== 1 and filter_apply(arr_contents, filter_items[i]) then omitted_arr_table = filter_apply(arr_contents, filter_items[i]) end
+ if i > 1 then
+ if filter_apply(arr_contents, filter_items[i]) then temp_filtered_contents = filter_apply(arr_contents, filter_items[i]) end
+ for j=1, #temp_filtered_contents do
+ for k=1, #omitted_arr_table do
+ if temp_filtered_contents[j] and omitted_arr_table[k] and temp_filtered_contents[j].found_sequence == omitted_arr_table[k].found_sequence then
+ table.remove(omitted_arr_table, k)
+ end
+ end
+ end
+ end
+ end
+
+ table.sort(omitted_arr_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+
+ return omitted_arr_table
+end
+
+function filter_stack(arr_contents, filter)
+ if not arr_contents or not arr_contents[1] or not filter or filter == 'all' or not filter:match('%%%+%%') then return false end
+ local stacked_arr_table = {}
+ local filter_items = {}
+
+ for f in filter:gmatch("[^%%%+%%\r+]+") do
+ table.insert(filter_items, f)
+ end
+
+ local unique_values = {}
+ local temp_filtered_contents = arr_contents
+ for i=1, #filter_items do
+ if filter_apply(arr_contents, filter_items[i]) then temp_filtered_contents = filter_apply(arr_contents, filter_items[i]) end
+ for j=1, #temp_filtered_contents do
+ if not has_value(unique_values, temp_filtered_contents[j].found_sequence) then
+ table.insert(unique_values, temp_filtered_contents[j].found_sequence)
+ table.insert(stacked_arr_table, temp_filtered_contents[j])
+ end
+ end
+ end
+ table.sort(stacked_arr_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+
+ return stacked_arr_table
+
+end
+
+function filter_apply(arr_contents, filter)
+ if not arr_contents or not arr_contents[1] or not filter or filter == 'all' then return false end
+ local filtered_arr_contents = {}
+
+ if filter == 'groups' then
+ for i = 1, #arr_contents do
+ if arr_contents[i].found_group then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter:match('/:group%%(.*)%%') then
+ filter = filter:match('/:group%%(.*)%%')
+ for i = 1, #arr_contents do
+ if arr_contents[i].found_group and filter == get_group_properties(tonumber(arr_contents[i].found_group)).name then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'keybinds' then
+ for i = 1, #arr_contents do
+ if arr_contents[i].found_slot then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'recents' then
+ table.sort(arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+ local unique_values = {}
+ local list_total = #arr_contents
+
+ if filePath == arr_contents[#arr_contents].found_path and tonumber(arr_contents[#arr_contents].found_time) == 0 then
+ list_total = list_total -1
+ end
+
+ for i = list_total, 1, -1 do
+ if not has_value(unique_values, arr_contents[i].found_path) then
+ table.insert(unique_values, arr_contents[i].found_path)
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ table.sort(filtered_arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+ end
+
+ if filter == 'distinct' then
+ table.sort(arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+ local unique_values = {}
+ local list_total = #arr_contents
+
+ if filePath == arr_contents[#arr_contents].found_path and tonumber(arr_contents[#arr_contents].found_time) == 0 then
+ list_total = list_total -1
+ end
+
+ for i = list_total, 1, -1 do
+ if arr_contents[i].found_directory and not has_value(unique_values, arr_contents[i].found_directory) and not starts_protocol(protocols, arr_contents[i].found_path) then
+ table.insert(unique_values, arr_contents[i].found_directory)
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ table.sort(filtered_arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end)
+ end
+
+ if filter == 'fileonly' then
+ for i = 1, #arr_contents do
+ if tonumber(arr_contents[i].found_time) == 0 then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'timeonly' then
+ for i = 1, #arr_contents do
+ if tonumber(arr_contents[i].found_time) > 0 then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'titleonly' then
+ for i = 1, #arr_contents do
+ if arr_contents[i].found_title then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'protocols' then
+ for i = 1, #arr_contents do
+ if starts_protocol(o.logging_protocols, arr_contents[i].found_path) then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'keywords' then
+ for i = 1, #arr_contents do
+ if contain_value(o.keywords_filter_list, arr_contents[i].found_line) then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ if filter == 'playing' then
+ for i = 1, #arr_contents do
+ if arr_contents[i].found_path == filePath then
+ table.insert(filtered_arr_contents, arr_contents[i])
+ end
+ end
+ end
+
+ return filtered_arr_contents
+end
+
+function get_osd_log_contents(filter, sort)
+ if not filter then filter = filterName end
+ if not sort then sort = get_list_sort(filter) end
+
+ local current_sort
+ osd_log_contents = read_log_table()
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ current_sort = 'added-asc'
+
+ if filter_log_contents(osd_log_contents, filter) then osd_log_contents = filter_log_contents(osd_log_contents, filter) end
+ if search_log_contents(osd_log_contents) then osd_log_contents = search_log_contents(osd_log_contents) end
+
+ if sort ~= current_sort then
+ list_sort(osd_log_contents, sort)
+ end
+end
+
+function get_list_sort(filter)
+ if not filter then filter = filterName end
+
+ if filter == 'keybinds' then
+ available_sorts = {'added-asc', 'added-desc', 'keybind-asc', 'keybind-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'}
+ else
+ available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'}
+ end
+
+ local sort
+ for i=1, #o.list_filters_sort do
+ if o.list_filters_sort[i][1] == filter then
+ if has_value(available_sorts, o.list_filters_sort[i][2]) then sort = o.list_filters_sort[i][2] end
+ break
+ end
+ end
+
+ if not sort and has_value(available_sorts, o.list_default_sort) then sort = o.list_default_sort end
+
+ if not sort then sort = 'added-asc' end
+
+ return sort
+end
+
+function parse_8601(timestamp)
+ if not string.match(timestamp, '^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$') then return false end
+ local inYear, inMonth, inDay, inHour, inMinute, inSecond, inZone = string.match(timestamp, '^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$')
+
+ local zHours, zMinutes = string.match(inZone, '^(.-):(%d%d)$')
+
+ local returnTime = os.time({year=inYear, month=inMonth, day=inDay, hour=inHour, min=inMinute, sec=inSecond, isdst=false})
+
+ if zHours then
+ returnTime = returnTime - ((tonumber(zHours)*3600) + (tonumber(zMinutes)*60))
+ end
+
+ return returnTime
+
+end
+
+function draw_list(arr_contents)
+ local osd_msg = ''
+ local osd_color = ''
+ local key = 0
+ local osd_text = string.format("{\\an%f{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_scale, o.text_scale, o.text_border, o.text_color)
+ local osd_cursor = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_cursor_scale, o.text_cursor_scale, o.text_cursor_border, o.text_cursor_color)
+ local osd_header = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.header_scale, o.header_scale, o.header_border, o.header_color)
+ local osd_msg_end = "{\\1c&HFFFFFF}"
+ local item_properties = {} --1.3# to hold all of the stuff that we extract from within this table, such as the osd_index, etc..
+
+ if o.header_text ~= '' then
+ osd_msg = osd_msg .. osd_header .. parse_header(o.header_text) .. osd_msg_end --1.3.0# made line break part of the config
+ end
+
+ if search_active and not arr_contents[1] then --1.3.1# changed osd_log_contents to arr_contents
+ osd_msg = osd_msg .. 'No search results found' .. osd_msg_end
+ end
+
+ local list_start
+ if o.list_middle_loader then
+ list_start = list_cursor - math.floor(o.list_show_amount / 2)
+ else
+ list_start = list_cursor - o.list_show_amount
+ end
+ local showall = false
+ local showrest = false
+ if list_start < 0 then list_start = 0 end
+ if #arr_contents <= o.list_show_amount then
+ list_start = 0
+ showall = true
+ end
+ if list_start > math.max(#arr_contents - o.list_show_amount - 1, 0) then
+ list_start = #arr_contents - o.list_show_amount
+ showrest = true
+ end
+ if list_start > 0 and not showall then
+ osd_msg = osd_msg .. o.list_sliced_prefix .. osd_msg_end
+ end
+ for i = list_start, list_start + o.list_show_amount - 1, 1 do
+ if i == #arr_contents then break end
+ item_properties["item"] = arr_contents[#arr_contents - i] --1.3# stores the targetted item
+ item_properties['index'] = i --1.3# stores the index of which the item is found in the arr_contents table
+
+ if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then
+ key = key + 1
+ if key == 10 then key = 0 end
+ item_properties['quickselect'] = key --1.3# added osd_key to item_properties to call it in parse_list_item
+ end
+
+ if i + 1 == list_cursor then
+ osd_color = osd_cursor
+ else
+ osd_color = osd_text
+ end
+
+ for j = 1, #list_highlight_cursor do
+ if list_highlight_cursor[j] and list_highlight_cursor[j][1] == i+1 then
+ osd_msg = osd_msg..osd_color..esc_string(o.text_highlight_pre_text)
+ end
+ end
+
+ if o.list_content_text ~= '' then --1.3# use parse_list_item to make the list customizable
+ osd_msg = osd_msg..osd_color..parse_list_item(o.list_content_text, item_properties) .. osd_msg_end --1.3.0# made line break part of the config
+ end
+
+ if i == list_start + o.list_show_amount - 1 and not showall and not showrest then
+ osd_msg = osd_msg .. o.list_sliced_suffix
+ end
+
+ end
+ mp.set_osd_ass(0, 0, osd_msg)
+end
+
+function list_empty_error_msg()
+ if osd_log_contents ~= nil and osd_log_contents[1] then return end
+ local msg_text
+ if filterName ~= 'all' then
+ msg_text = filterName .. " filter in Bookmark Empty"
+ else
+ msg_text = "Bookmark Empty"
+ end
+ msg.info(msg_text)
+ if o.osd_messages == true and not list_drawn then
+ mp.osd_message(msg_text)
+ end
+end
+
+function display_list(filter, sort, action)
+ if not filter then filter = 'all' end
+ if not sortName then sortName = get_list_sort(filter) end
+
+ local prev_sort = sortName
+ if not has_value(available_sorts, prev_sort) then prev_sort = get_list_sort() end
+
+ if not sort then sort = get_list_sort(filter) end
+ sortName = sort
+
+ local prev_filter = filterName
+ filterName = filter
+
+ get_osd_log_contents(filter, sort)
+
+ if action ~= 'hide-osd' then
+ if not osd_log_contents or not osd_log_contents[1] then
+ list_empty_error_msg()
+ filterName = prev_filter
+ get_osd_log_contents(filterName)
+ return
+ end
+ end
+ if not osd_log_contents and not search_active or not osd_log_contents[1] and not search_active then return end
+
+ if not has_value(o.filters_and_sequence, filter) then
+ table.insert(o.filters_and_sequence, filter)
+ end
+
+ local insert_new = false
+
+ local trigger_close_list = false
+ local trigger_initial_list = false
+
+
+ if not list_pages or not list_pages[1] then
+ table.insert(list_pages, {filter, 1, 1, {}, sort})
+ else
+ for i = 1, #list_pages do
+ if list_pages[i][1] == filter then
+ list_pages[i][3] = list_pages[i][3]+1
+ insert_new = false
+ break
+ else
+ insert_new = true
+ end
+ end
+ end
+
+ if insert_new then table.insert(list_pages, {filter, 1, 1, {}, sort}) end
+
+ for i = 1, #list_pages do
+ if not search_active and list_pages[i][1] == prev_filter then
+ list_pages[i][2] = list_cursor
+ list_pages[i][4] = list_highlight_cursor
+ list_pages[i][5] = prev_sort
+ end
+ if list_pages[i][1] ~= filter then
+ list_pages[i][3] = 0
+ end
+ if list_pages[i][3] == 2 and filter == 'all' and o.main_list_keybind_twice_exits then
+ trigger_close_list = true
+ elseif list_pages[i][3] == 2 and list_pages[1][1] == filter then
+ trigger_close_list = true
+ elseif list_pages[i][3] == 2 then
+ trigger_initial_list = true
+ end
+ end
+
+ if trigger_initial_list then
+ display_list(list_pages[1][1], nil, 'hide-osd')
+ return
+ end
+
+ if trigger_close_list then
+ list_close_and_trash_collection()
+ return
+ end
+
+ if not search_active then get_page_properties(filter) else update_search_results('','') end
+ draw_list(osd_log_contents)
+ if utils.shared_script_property_set then
+ utils.shared_script_property_set('simplebookmark-menu-open', 'yes')
+ end
+ mp.set_property('user-data/simplebookmark/menu-open', 'yes')
+ if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'no', 'no_osd') end
+ list_drawn = true
+ if not search_active then get_list_keybinds() end
+end
+
+--End of LogManager (Read and Format the List from Log)--
+
+--LogManager Navigation--
+function select(pos, action)
+ if not search_active then
+ if not osd_log_contents or not osd_log_contents[1] then
+ list_close_and_trash_collection()
+ return
+ end
+ end
+
+ local list_cursor_temp = list_cursor + pos
+ if list_cursor_temp > 0 and list_cursor_temp <= #osd_log_contents then
+ list_cursor = list_cursor_temp
+
+ if action == 'highlight' then
+ if not has_value(list_highlight_cursor, list_cursor, 1) then
+ if pos > -1 then
+ for i = pos, 1, -1 do
+ if not has_value(list_highlight_cursor, list_cursor-i, 1) then
+ table.insert(list_highlight_cursor, {list_cursor-i, osd_log_contents[#osd_log_contents+1+i - list_cursor]})
+ end
+ end
+ else
+ for i = pos, -1, 1 do
+ if not has_value(list_highlight_cursor, list_cursor-i, 1) then
+ table.insert(list_highlight_cursor, {list_cursor-i, osd_log_contents[#osd_log_contents+1+i - list_cursor]})
+ end
+ end
+ end
+ table.insert(list_highlight_cursor, {list_cursor, osd_log_contents[#osd_log_contents+1 - list_cursor]})
+ else
+ for i=1, #list_highlight_cursor do
+ if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor then
+ table.remove(list_highlight_cursor, i)
+ end
+ end
+ if pos > -1 then
+ for i=1, #list_highlight_cursor do
+ for j = pos, 1, -1 do
+ if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor-j then
+ table.remove(list_highlight_cursor, i)
+ end
+ end
+ end
+ else
+ for i=#list_highlight_cursor, 1, -1 do
+ for j = pos, -1, 1 do
+ if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor-j then
+ table.remove(list_highlight_cursor, i)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ if o.loop_through_list then
+ if list_cursor_temp > #osd_log_contents then
+ list_cursor = 1
+ elseif list_cursor_temp < 1 then
+ list_cursor = #osd_log_contents
+ end
+ end
+
+ draw_list(osd_log_contents)
+end
+
+function list_move_up(action)
+ select(-1, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_move_down(action)
+ select(1, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_move_first(action)
+ select(1 - list_cursor, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_move_last(action)
+ select(#osd_log_contents - list_cursor, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_page_up(action)
+ select(list_start + 1 - list_cursor, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_page_down(action)
+ if o.list_middle_loader then
+ if #osd_log_contents < o.list_show_amount then
+ select(#osd_log_contents - list_cursor, action)
+ else
+ select(o.list_show_amount + list_start - list_cursor, action)
+ end
+ else
+ if o.list_show_amount > list_cursor then
+ select(o.list_show_amount - list_cursor, action)
+ elseif #osd_log_contents - list_cursor >= o.list_show_amount then
+ select(o.list_show_amount, action)
+ else
+ select(#osd_log_contents - list_cursor, action)
+ end
+ end
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_highlight_all()
+ get_osd_log_contents(filterName)
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ if #list_highlight_cursor < #osd_log_contents then
+ for i=1, #osd_log_contents do
+ if not has_value(list_highlight_cursor, i, 1) then
+ table.insert(list_highlight_cursor, {i, osd_log_contents[#osd_log_contents+1-i]})
+ end
+ end
+ select(0)
+ else
+ list_unhighlight_all()
+ end
+end
+
+function list_unhighlight_all()
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+ list_highlight_cursor = {}
+ select(0)
+end
+--End of LogManager Navigation--
+
+--LogManager Actions--
+function load(list_cursor, add_playlist, target_time)
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ if not target_time then
+ if not osd_log_contents[#osd_log_contents - list_cursor + 1] then return end --1.3.0# fixes crash when loading an entry that doesn't exist
+ seekTime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time) + o.resume_offset
+ if (seekTime < 0) then
+ seekTime = 0
+ end
+ else
+ seekTime = target_time
+ end
+ if file_exists(osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) or starts_protocol(protocols, osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) then
+ local list_filename, list_filepath, list_filetitle = get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.3# use the name that automatically falls back instead for osd printing or msg (solves the issue that causes concatinating found_name to crash because it sometimes doesn't exist due to parsing changes)
+ if not add_playlist then
+ if o.preserve_video_settings then mp.command("write-watch-later-config") end--1.3.1# option to preserve video settings by using write-watch-later-config when loading bookmark replaces current file #84
+ if filePath ~= osd_log_contents[#osd_log_contents - list_cursor + 1].found_path then
+ mp.commandv('loadfile', osd_log_contents[#osd_log_contents - list_cursor + 1].found_path)
+ resume_selected = true
+ else
+ mp.commandv('seek', seekTime, 'absolute', 'exact')
+ list_close_and_trash_collection()
+ end
+ if o.osd_messages == true then
+ mp.osd_message('Loaded:\n' .. list_filename.. ' 🕒 ' .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
+ end
+ msg.info('Loaded the below file:\n' .. list_filename .. ' | '.. format_time(seekTime))
+ else
+ mp.commandv('loadfile', osd_log_contents[#osd_log_contents - list_cursor + 1].found_path, 'append-play')
+ if o.osd_messages == true then
+ mp.osd_message('Added into Playlist:\n'..list_filename..' ')
+ end
+ msg.info('Added the below file into playlist:\n' .. list_filepath)
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message('File Doesn\'t Exist:\n' .. osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) --1.3# cant use the list_filepath because file doesn't exist so it returns nil
+ end
+ msg.info('The file below doesn\'t seem to exist:\n' .. osd_log_contents[#osd_log_contents - list_cursor + 1].found_path)
+ return
+ end
+end
+
+function list_select()
+ load(list_cursor)
+end
+
+function list_add_playlist(action)
+ if not action then
+ load(list_cursor, true)
+ elseif action == 'highlight' then
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+ local file_ignored_total = 0
+
+ for i=1, #list_highlight_cursor do
+ if file_exists(list_highlight_cursor[i][2].found_path) or starts_protocol(protocols, list_highlight_cursor[i][2].found_path) then
+ mp.commandv("loadfile", list_highlight_cursor[i][2].found_path, "append-play")
+ else
+ msg.warn('The below file was not added into playlist as it does not seem to exist:\n' .. list_highlight_cursor[i][2].found_path)
+ file_ignored_total = file_ignored_total + 1
+ end
+ end
+ if o.osd_messages == true then
+ if file_ignored_total > 0 then
+ mp.osd_message('Added into Playlist '..#list_highlight_cursor - file_ignored_total..' Item/s\nIgnored '..file_ignored_total.. " Item/s That Do Not Exist")
+ else
+ mp.osd_message('Added into Playlist '..#list_highlight_cursor - file_ignored_total..' Item/s')
+ end
+ end
+ if file_ignored_total > 0 then
+ msg.warn('Ignored a total of '..file_ignored_total.. " Item/s that does not seem to exist")
+ end
+ msg.info('Added into playlist a total of '..#list_highlight_cursor - file_ignored_total..' item/s')
+ end
+end
+
+function same_path_log_delete(target_path, entry_limit, arr_contents)
+ --1.2.5# seperate function for entry_limit
+ if not target_path then return msg.error('same_path_log_delete no target_path defined') end
+ if not arr_contents then --1.2.6# ability to pass array (usually automatically defining array here is fine, but just for performance sake when calling multiple functions that use the same array)
+ arr_contents = read_log_table()
+ if not arr_contents or not arr_contents[1] then return end
+ end
+
+ --1.2.7# return deleted_entries for o.overwrite_preserve_properties option
+ local deleted_entries = {}
+ local trigger_delete = false
+
+ if entry_limit and entry_limit > -1 then
+ local entries_found = 0
+ for i = #arr_contents, 1, -1 do
+ if arr_contents[i].found_path == target_path and entries_found < entry_limit then
+ entries_found = entries_found + 1
+ elseif arr_contents[i].found_path == target_path and entries_found >= entry_limit then
+ table.insert(deleted_entries, arr_contents[i]) --1.2.7# store entries that will be deleted in a new array
+ table.remove(arr_contents,i)
+ trigger_delete = true
+ end
+ end
+ end
+
+ if not trigger_delete then return end
+ local f = io.open(log_fullpath, "w+")
+ if arr_contents ~= nil and arr_contents[1] then
+ for i = 1, #arr_contents do
+ f:write(("%s\n"):format(arr_contents[i].found_line))
+ end
+ end
+ f:close()
+ return deleted_entries
+end
+
+
+function find_entry(round, target_path, target_time) --1.2.6# changed it to find_entry to have the sequence and any other additional property
+ --1.2.5# get the entry log sequence which is basically the id using path and time
+ if not target_path or not target_time then return msg.error('find_entry no target_path or target_time defined') end
+ local temp_log_contents = read_log_table()
+ if not temp_log_contents or not temp_log_contents[1] then return end
+
+ for i = #temp_log_contents, 1, -1 do
+ if not round then
+ if temp_log_contents[i].found_path == target_path and tonumber(temp_log_contents[i].found_time) == target_time then
+ return temp_log_contents[i]
+ end
+ else
+ if temp_log_contents[i].found_path == target_path and math.floor(tonumber(temp_log_contents[i].found_time)) == target_time then
+ return temp_log_contents[i]
+ end
+ end
+ end
+end
+
+
+function delete_log_entry(target_sequence, arr_contents)
+ --1.2.5# new function to delete based on sequence which is (id)
+ if not target_sequence then return end --1.2.5# if no sequence found then just exit the function
+ if not arr_contents then --1.2.5# ability to pass an array instead of looping through
+ arr_contents = read_log_table()
+ if not arr_contents or not arr_contents[1] then return end
+ end
+
+ table.remove(arr_contents, target_sequence)
+
+ local f = io.open(log_fullpath, "w+")
+ if arr_contents ~= nil and arr_contents[1] then
+ for i = 1, #arr_contents do
+ f:write(("%s\n"):format(arr_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+
+function delete_log_entry_highlighted()
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+ local temp_log_contents = read_log_table()
+ if not temp_log_contents or not temp_log_contents[1] then return end
+
+ local log_contents_length = #temp_log_contents
+
+ for i = 1, log_contents_length do
+ for j=1, #list_highlight_cursor do
+ if temp_log_contents[log_contents_length+1-i] then
+ if temp_log_contents[log_contents_length+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then
+ table.remove(temp_log_contents, log_contents_length+1-i)
+ end
+ end
+ end
+ end
+
+ msg.info("Deleted "..#list_highlight_cursor.." Item/s")
+
+ list_unhighlight_all()
+
+ local f = io.open(log_fullpath, "w+")
+ if temp_log_contents ~= nil and temp_log_contents[1] then
+ for i = 1, #temp_log_contents do
+ f:write(("%s\n"):format(temp_log_contents[i].found_line))
+ end
+ end
+ f:close()
+
+end
+
+function delete_selected()
+ --1.2.5# replace with new delete_log_entry that uses sequence id, and used local variables with or statement just in case
+ local list_sequence = osd_log_contents[#osd_log_contents - list_cursor + 1].found_sequence
+ local list_filepath = osd_log_contents[#osd_log_contents - list_cursor + 1].found_path or ""
+ local list_seektime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time) or 0
+
+ if not list_sequence then
+ msg.info("Failed to delete")
+ return
+ end
+ delete_log_entry(list_sequence)
+ msg.info("Deleted \"" .. list_filepath .. "\" | " .. format_time(list_seektime))
+end
+
+function list_delete(action)
+ if not action then
+ delete_selected()
+ elseif action == 'highlight' then
+ delete_log_entry_highlighted()
+ end
+ get_osd_log_contents()
+ if #osd_log_contents == 0 then
+ display_list('all')
+ select(0)
+ elseif list_cursor < #osd_log_contents + 1 then
+ select(0)
+ else
+ list_move_last()
+ end
+end
+
+function get_total_duration(action)
+ if not osd_log_contents or not osd_log_contents[1] then return 0 end
+ local list_total_duration = 0
+ if action == 'found_time' or action == 'found_length' or action == 'found_remaining' then
+ for i = #osd_log_contents, 1, -1 do
+ if tonumber(osd_log_contents[i][action]) > 0 then
+ list_total_duration = list_total_duration + osd_log_contents[i][action]
+ end
+ end
+ end
+ return list_total_duration
+end
+
+function list_cycle_sort()
+ if filterName == 'keybinds' then
+ available_sorts = {'added-asc', 'added-desc', 'keybind-asc', 'keybind-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'}
+ else
+ available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'}
+ end
+
+ local next_sort
+ for i = 1, #available_sorts do
+ if sortName == available_sorts[i] then
+ if i == #available_sorts then
+ next_sort = available_sorts[1]
+ break
+ else
+ next_sort = available_sorts[i+1]
+ break
+ end
+ end
+ end
+ if not next_sort then return end
+ get_osd_log_contents(filterName, next_sort)
+ sortName = next_sort
+ update_list_highlist_cursor()
+ select(0)
+end
+
+function update_list_highlist_cursor()
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+
+ local temp_list_highlight_cursor = {}
+ for i = 1, #osd_log_contents do
+ for j=1, #list_highlight_cursor do
+ if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then
+ table.insert(temp_list_highlight_cursor, {i, list_highlight_cursor[j][2]})
+ end
+ end
+ end
+
+ list_highlight_cursor = temp_list_highlight_cursor
+end
+
+--End of LogManager Actions--
+
+--LogManager Filter Functions--
+function get_page_properties(filter)
+ if not filter then return end
+ for i=1, #list_pages do
+ if list_pages[i][1] == filter then
+ list_cursor = list_pages[i][2]
+ list_highlight_cursor = list_pages[i][4]
+ sortName = list_pages[i][5]
+ end
+ end
+ if list_cursor > #osd_log_contents then
+ list_move_last()
+ end
+end
+
+function select_filter_sequence(pos)
+ if not list_drawn then return end
+ local curr_pos
+ local target_pos
+
+ for i = 1, #o.filters_and_sequence do
+ if filterName == o.filters_and_sequence[i] then
+ curr_pos = i
+ end
+ end
+
+ if curr_pos and pos > -1 then
+ for i = curr_pos, #o.filters_and_sequence do
+ if o.filters_and_sequence[i + pos] then
+ get_osd_log_contents(o.filters_and_sequence[i + pos])
+ if osd_log_contents ~= nil and osd_log_contents[1] then
+ target_pos = i + pos
+ break
+ end
+ end
+ end
+ elseif curr_pos and pos < 0 then
+ for i = curr_pos, 0, -1 do
+ if o.filters_and_sequence[i + pos] then
+ get_osd_log_contents(o.filters_and_sequence[i + pos])
+ if osd_log_contents ~= nil and osd_log_contents[1] then
+ target_pos = i + pos
+ break
+ end
+ end
+ end
+ end
+
+ if o.loop_through_filters then
+ if not target_pos and pos > -1 or target_pos and target_pos > #o.filters_and_sequence then
+ for i = 1, #o.filters_and_sequence do
+ get_osd_log_contents(o.filters_and_sequence[i])
+ if osd_log_contents ~= nil and osd_log_contents[1] then
+ target_pos = i
+ break
+ end
+ end
+ end
+ if not target_pos and pos < 0 or target_pos and target_pos < 1 then
+ for i = #o.filters_and_sequence, 1, -1 do
+ get_osd_log_contents(o.filters_and_sequence[i])
+ if osd_log_contents ~= nil and osd_log_contents[1] then
+ target_pos = i
+ break
+ end
+ end
+ end
+ end
+
+ if o.filters_and_sequence[target_pos] then
+ display_list(o.filters_and_sequence[target_pos], nil, 'hide-osd')
+ end
+end
+
+function list_filter_next()
+ select_filter_sequence(1)
+end
+function list_filter_previous()
+ select_filter_sequence(-1)
+end
+--End of LogManager Filter Functions--
+
+--LogManager (List Bind and Unbind)--
+function get_list_keybinds()
+ bind_keys(o.list_ignored_keybind, 'ignore')
+ bind_keys(o.list_move_up_keybind, 'move-up', list_move_up, 'repeatable')
+ bind_keys(o.list_move_down_keybind, 'move-down', list_move_down, 'repeatable')
+ bind_keys(o.list_move_first_keybind, 'move-first', list_move_first, 'repeatable')
+ bind_keys(o.list_move_last_keybind, 'move-last', list_move_last, 'repeatable')
+ bind_keys(o.list_page_up_keybind, 'page-up', list_page_up, 'repeatable')
+ bind_keys(o.list_page_down_keybind, 'page-down', list_page_down, 'repeatable')
+ bind_keys(o.list_select_keybind, 'list-select', list_select)
+ bind_keys(o.list_add_playlist_keybind, 'list-add-playlist', list_add_playlist)
+ bind_keys(o.list_add_playlist_highlighted_keybind, 'list-add-playlist-highlight', function()list_add_playlist('highlight')end)
+ bind_keys(o.list_delete_keybind, 'list-delete', list_delete)
+ bind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight', function()list_delete('highlight')end)
+ bind_keys(o.next_filter_sequence_keybind, 'list-filter-next', list_filter_next)
+ bind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous', list_filter_previous)
+ bind_keys(o.list_search_activate_keybind, 'list-search-activate', list_search_activate)
+ bind_keys(o.list_highlight_all_keybind, 'list-highlight-all', list_highlight_all)
+ bind_keys(o.list_unhighlight_all_keybind, 'list-unhighlight-all', list_unhighlight_all)
+ bind_keys(o.list_cycle_sort_keybind, 'list-cycle-sort', list_cycle_sort)
+ bind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove', slot_remove)
+ bind_keys(o.keybinds_remove_highlighted_keybind, 'keybind-slot-remove-highlight', function()slot_remove('highlight')end)
+ bind_keys(o.list_group_add_cycle_keybind, 'group-add-cycle', list_group_add_cycle)
+ bind_keys(o.list_group_add_cycle_highlighted_keybind, 'group-add-cycle-highlight', function()list_group_add_cycle('highlight')end)
+ bind_keys(o.list_groups_remove_keybind, 'group-remove', group_remove)
+ bind_keys(o.list_groups_remove_highlighted_keybind, 'group-remove-highlight', function()group_remove('highlight')end)
+
+ for i = 1, #o.groups_list_and_keybind do
+ if not o.groups_list_and_keybind[i][2] then break end
+ mp.add_forced_key_binding(o.groups_list_and_keybind[i][2], 'group-add-'..i, function()group_add(i)end)
+ end
+ for i = 1, #o.groups_list_and_keybind do
+ if not o.groups_list_and_keybind[i][3] then break end
+ mp.add_forced_key_binding(o.groups_list_and_keybind[i][3], 'group-add-highlight-'..i, function()group_add(i, 'highlight')end)
+ end
+
+ for i = 1, #o.list_highlight_move_keybind do
+ for j = 1, #o.list_move_up_keybind do
+ mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_up_keybind[j], 'highlight-move-up'..j, function()list_move_up('highlight') end, 'repeatable')
+ end
+ for j = 1, #o.list_move_down_keybind do
+ mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_down_keybind[j], 'highlight-move-down'..j, function()list_move_down('highlight') end, 'repeatable')
+ end
+ for j = 1, #o.list_move_first_keybind do
+ mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_first_keybind[j], 'highlight-move-first'..j, function()list_move_first('highlight') end, 'repeatable')
+ end
+ for j = 1, #o.list_move_last_keybind do
+ mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_last_keybind[j], 'highlight-move-last'..j, function()list_move_last('highlight') end, 'repeatable')
+ end
+ for j = 1, #o.list_page_up_keybind do
+ mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_page_up_keybind[j], 'highlight-page-up'..j, function()list_page_up('highlight') end, 'repeatable')
+ end
+ for j = 1, #o.list_page_down_keybind do
+ mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_page_down_keybind[j], 'highlight-page-down'..j, function()list_page_down('highlight') end, 'repeatable')
+ end
+ end
+
+ if not search_active then
+ bind_keys(o.list_close_keybind, 'list-close', list_close_and_trash_collection)
+ end
+
+ for i = 1, #o.list_filter_jump_keybind do
+ mp.add_forced_key_binding(o.list_filter_jump_keybind[i][1], 'list-filter-jump'..i, function()display_list(o.list_filter_jump_keybind[i][2]) end)
+ end
+
+ for i = 1, #o.open_list_keybind do
+ if i == 1 then
+ mp.remove_key_binding('open-list')
+ else
+ mp.remove_key_binding('open-list'..i)
+ end
+ end
+
+ if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then
+ mp.add_forced_key_binding("1", "recent-1", function()load(list_start + 1) end)
+ mp.add_forced_key_binding("2", "recent-2", function()load(list_start + 2) end)
+ mp.add_forced_key_binding("3", "recent-3", function()load(list_start + 3) end)
+ mp.add_forced_key_binding("4", "recent-4", function()load(list_start + 4) end)
+ mp.add_forced_key_binding("5", "recent-5", function()load(list_start + 5) end)
+ mp.add_forced_key_binding("6", "recent-6", function()load(list_start + 6) end)
+ mp.add_forced_key_binding("7", "recent-7", function()load(list_start + 7) end)
+ mp.add_forced_key_binding("8", "recent-8", function()load(list_start + 8) end)
+ mp.add_forced_key_binding("9", "recent-9", function()load(list_start + 9) end)
+ mp.add_forced_key_binding("0", "recent-0", function()load(list_start + 10) end)
+ end
+end
+
+function unbind_list_keys()
+ unbind_keys(o.list_ignored_keybind, 'ignore')
+ unbind_keys(o.list_move_up_keybind, 'move-up')
+ unbind_keys(o.list_move_down_keybind, 'move-down')
+ unbind_keys(o.list_move_first_keybind, 'move-first')
+ unbind_keys(o.list_move_last_keybind, 'move-last')
+ unbind_keys(o.list_page_up_keybind, 'page-up')
+ unbind_keys(o.list_page_down_keybind, 'page-down')
+ unbind_keys(o.list_select_keybind, 'list-select')
+ unbind_keys(o.list_add_playlist_keybind, 'list-add-playlist')
+ unbind_keys(o.list_add_playlist_highlighted_keybind, 'list-add-playlist-highlight')
+ unbind_keys(o.list_delete_keybind, 'list-delete')
+ unbind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight')
+ unbind_keys(o.list_close_keybind, 'list-close')
+ unbind_keys(o.next_filter_sequence_keybind, 'list-filter-next')
+ unbind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous')
+ unbind_keys(o.list_highlight_all_keybind, 'list-highlight-all')
+ unbind_keys(o.list_highlight_all_keybind, 'list-unhighlight-all')
+ unbind_keys(o.list_cycle_sort_keybind, 'list-cycle-sort')
+ unbind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove')
+ unbind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove-highlight')
+
+ unbind_keys(o.list_group_add_cycle_keybind, 'group-add-cycle')
+ unbind_keys(o.list_group_add_cycle_highlighted_keybind, 'group-add-cycle-highlight')
+ unbind_keys(o.list_groups_remove_keybind, 'group-remove')
+ unbind_keys(o.list_groups_remove_highlighted_keybind, 'group-remove-highlight')
+
+ for i = 1, #o.groups_list_and_keybind do
+ if not o.groups_list_and_keybind[i][2] then break end
+ mp.remove_key_binding('group-add-'..i)
+ end
+ for i = 1, #o.groups_list_and_keybind do
+ if not o.groups_list_and_keybind[i][3] then break end
+ mp.remove_key_binding('group-add-highlight-'..i)
+ end
+
+ for i = 1, #o.list_move_up_keybind do
+ mp.remove_key_binding('highlight-move-up'..i)
+ end
+ for i = 1, #o.list_move_down_keybind do
+ mp.remove_key_binding('highlight-move-down'..i)
+ end
+ for i = 1, #o.list_move_first_keybind do
+ mp.remove_key_binding('highlight-move-first'..i)
+ end
+ for i = 1, #o.list_move_last_keybind do
+ mp.remove_key_binding('highlight-move-last'..i)
+ end
+ for i = 1, #o.list_page_up_keybind do
+ mp.remove_key_binding('highlight-page-up'..i)
+ end
+ for i = 1, #o.list_page_down_keybind do
+ mp.remove_key_binding('highlight-page-down'..i)
+ end
+
+ for i = 1, #o.list_filter_jump_keybind do
+ mp.remove_key_binding('list-filter-jump'..i)
+ end
+
+ for i = 1, #o.open_list_keybind do
+ if i == 1 then
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list', function()display_list(o.open_list_keybind[i][2]) end)
+ else
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list'..i, function()display_list(o.open_list_keybind[i][2]) end)
+ end
+ end
+
+ if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then
+ mp.remove_key_binding("recent-1")
+ mp.remove_key_binding("recent-2")
+ mp.remove_key_binding("recent-3")
+ mp.remove_key_binding("recent-4")
+ mp.remove_key_binding("recent-5")
+ mp.remove_key_binding("recent-6")
+ mp.remove_key_binding("recent-7")
+ mp.remove_key_binding("recent-8")
+ mp.remove_key_binding("recent-9")
+ mp.remove_key_binding("recent-0")
+ end
+end
+
+function list_close_and_trash_collection()
+ if utils.shared_script_property_set then
+ utils.shared_script_property_set('simplebookmark-menu-open', 'no')
+ end
+ mp.set_property('user-data/simplebookmark/menu-open', 'no')
+ if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'yes', 'no_osd') end
+ unbind_list_keys()
+ unbind_search_keys()
+ mp.set_osd_ass(0, 0, "")
+ list_drawn = false
+ list_cursor = 1
+ list_start = 0
+ filterName = 'all'
+ list_pages = {}
+ search_string = ''
+ search_active = false
+ list_highlight_cursor = {}
+ sortName = nil
+end
+--End of LogManager (List Bind and Unbind)--
+
+--LogManager Search Feature--
+function list_search_exit()
+ search_active = false
+ get_osd_log_contents(filterName)
+ get_page_properties(filterName)
+ select(0)
+ unbind_search_keys()
+ get_list_keybinds()
+end
+
+function list_search_not_typing_mode(auto_triggered)
+ if auto_triggered then
+ if search_string ~= '' and osd_log_contents[1] then
+ search_active = 'not_typing'
+ elseif not osd_log_contents[1] then
+ return
+ else
+ search_active = false
+ end
+ else
+ if search_string ~= '' then
+ search_active = 'not_typing'
+ else
+ search_active = false
+ end
+ end
+ select(0)
+ unbind_search_keys()
+ get_list_keybinds()
+end
+
+function list_search_activate()
+ if not list_drawn then return end
+ if search_active == 'typing' then list_search_exit() return end
+ search_active = 'typing'
+
+ for i = 1, #list_pages do
+ if list_pages[i][1] == filterName then
+ list_pages[i][2] = list_cursor
+ list_pages[i][4] = list_highlight_cursor
+ list_pages[i][5] = sortName
+ end
+ end
+
+ update_search_results('','')
+ bind_search_keys()
+end
+
+function update_search_results(character, action)
+ if not character then character = '' end
+ if action == 'string_del' then
+ search_string = search_string:sub(1, -2)
+ end
+ search_string = search_string..character
+ local prev_contents_length = #osd_log_contents
+ get_osd_log_contents(filterName)
+
+ if prev_contents_length ~= #osd_log_contents then
+ list_highlight_cursor = {}
+ end
+
+ if character ~= '' and #osd_log_contents > 0 or action ~= nil and #osd_log_contents > 0 then
+ select(1-list_cursor)
+ elseif #osd_log_contents == 0 then
+ list_cursor = 0
+ select(list_cursor)
+ else
+ select(0)
+ end
+end
+
+function bind_search_keys()
+ mp.add_forced_key_binding('a', 'search_string_a', function() update_search_results('a') end, 'repeatable')
+ mp.add_forced_key_binding('b', 'search_string_b', function() update_search_results('b') end, 'repeatable')
+ mp.add_forced_key_binding('c', 'search_string_c', function() update_search_results('c') end, 'repeatable')
+ mp.add_forced_key_binding('d', 'search_string_d', function() update_search_results('d') end, 'repeatable')
+ mp.add_forced_key_binding('e', 'search_string_e', function() update_search_results('e') end, 'repeatable')
+ mp.add_forced_key_binding('f', 'search_string_f', function() update_search_results('f') end, 'repeatable')
+ mp.add_forced_key_binding('g', 'search_string_g', function() update_search_results('g') end, 'repeatable')
+ mp.add_forced_key_binding('h', 'search_string_h', function() update_search_results('h') end, 'repeatable')
+ mp.add_forced_key_binding('i', 'search_string_i', function() update_search_results('i') end, 'repeatable')
+ mp.add_forced_key_binding('j', 'search_string_j', function() update_search_results('j') end, 'repeatable')
+ mp.add_forced_key_binding('k', 'search_string_k', function() update_search_results('k') end, 'repeatable')
+ mp.add_forced_key_binding('l', 'search_string_l', function() update_search_results('l') end, 'repeatable')
+ mp.add_forced_key_binding('m', 'search_string_m', function() update_search_results('m') end, 'repeatable')
+ mp.add_forced_key_binding('n', 'search_string_n', function() update_search_results('n') end, 'repeatable')
+ mp.add_forced_key_binding('o', 'search_string_o', function() update_search_results('o') end, 'repeatable')
+ mp.add_forced_key_binding('p', 'search_string_p', function() update_search_results('p') end, 'repeatable')
+ mp.add_forced_key_binding('q', 'search_string_q', function() update_search_results('q') end, 'repeatable')
+ mp.add_forced_key_binding('r', 'search_string_r', function() update_search_results('r') end, 'repeatable')
+ mp.add_forced_key_binding('s', 'search_string_s', function() update_search_results('s') end, 'repeatable')
+ mp.add_forced_key_binding('t', 'search_string_t', function() update_search_results('t') end, 'repeatable')
+ mp.add_forced_key_binding('u', 'search_string_u', function() update_search_results('u') end, 'repeatable')
+ mp.add_forced_key_binding('v', 'search_string_v', function() update_search_results('v') end, 'repeatable')
+ mp.add_forced_key_binding('w', 'search_string_w', function() update_search_results('w') end, 'repeatable')
+ mp.add_forced_key_binding('x', 'search_string_x', function() update_search_results('x') end, 'repeatable')
+ mp.add_forced_key_binding('y', 'search_string_y', function() update_search_results('y') end, 'repeatable')
+ mp.add_forced_key_binding('z', 'search_string_z', function() update_search_results('z') end, 'repeatable')
+
+ mp.add_forced_key_binding('A', 'search_string_A', function() update_search_results('A') end, 'repeatable')
+ mp.add_forced_key_binding('B', 'search_string_B', function() update_search_results('B') end, 'repeatable')
+ mp.add_forced_key_binding('C', 'search_string_C', function() update_search_results('C') end, 'repeatable')
+ mp.add_forced_key_binding('D', 'search_string_D', function() update_search_results('D') end, 'repeatable')
+ mp.add_forced_key_binding('E', 'search_string_E', function() update_search_results('E') end, 'repeatable')
+ mp.add_forced_key_binding('F', 'search_string_F', function() update_search_results('F') end, 'repeatable')
+ mp.add_forced_key_binding('G', 'search_string_G', function() update_search_results('G') end, 'repeatable')
+ mp.add_forced_key_binding('H', 'search_string_H', function() update_search_results('H') end, 'repeatable')
+ mp.add_forced_key_binding('I', 'search_string_I', function() update_search_results('I') end, 'repeatable')
+ mp.add_forced_key_binding('J', 'search_string_J', function() update_search_results('J') end, 'repeatable')
+ mp.add_forced_key_binding('K', 'search_string_K', function() update_search_results('K') end, 'repeatable')
+ mp.add_forced_key_binding('L', 'search_string_L', function() update_search_results('L') end, 'repeatable')
+ mp.add_forced_key_binding('M', 'search_string_M', function() update_search_results('M') end, 'repeatable')
+ mp.add_forced_key_binding('N', 'search_string_N', function() update_search_results('N') end, 'repeatable')
+ mp.add_forced_key_binding('O', 'search_string_O', function() update_search_results('O') end, 'repeatable')
+ mp.add_forced_key_binding('P', 'search_string_P', function() update_search_results('P') end, 'repeatable')
+ mp.add_forced_key_binding('Q', 'search_string_Q', function() update_search_results('Q') end, 'repeatable')
+ mp.add_forced_key_binding('R', 'search_string_R', function() update_search_results('R') end, 'repeatable')
+ mp.add_forced_key_binding('S', 'search_string_S', function() update_search_results('S') end, 'repeatable')
+ mp.add_forced_key_binding('T', 'search_string_T', function() update_search_results('T') end, 'repeatable')
+ mp.add_forced_key_binding('U', 'search_string_U', function() update_search_results('U') end, 'repeatable')
+ mp.add_forced_key_binding('V', 'search_string_V', function() update_search_results('V') end, 'repeatable')
+ mp.add_forced_key_binding('W', 'search_string_W', function() update_search_results('W') end, 'repeatable')
+ mp.add_forced_key_binding('X', 'search_string_X', function() update_search_results('X') end, 'repeatable')
+ mp.add_forced_key_binding('Y', 'search_string_Y', function() update_search_results('Y') end, 'repeatable')
+ mp.add_forced_key_binding('Z', 'search_string_Z', function() update_search_results('Z') end, 'repeatable')
+
+ mp.add_forced_key_binding('1', 'search_string_1', function() update_search_results('1') end, 'repeatable')
+ mp.add_forced_key_binding('2', 'search_string_2', function() update_search_results('2') end, 'repeatable')
+ mp.add_forced_key_binding('3', 'search_string_3', function() update_search_results('3') end, 'repeatable')
+ mp.add_forced_key_binding('4', 'search_string_4', function() update_search_results('4') end, 'repeatable')
+ mp.add_forced_key_binding('5', 'search_string_5', function() update_search_results('5') end, 'repeatable')
+ mp.add_forced_key_binding('6', 'search_string_6', function() update_search_results('6') end, 'repeatable')
+ mp.add_forced_key_binding('7', 'search_string_7', function() update_search_results('7') end, 'repeatable')
+ mp.add_forced_key_binding('8', 'search_string_8', function() update_search_results('8') end, 'repeatable')
+ mp.add_forced_key_binding('9', 'search_string_9', function() update_search_results('9') end, 'repeatable')
+ mp.add_forced_key_binding('0', 'search_string_0', function() update_search_results('0') end, 'repeatable')
+
+ mp.add_forced_key_binding('SPACE', 'search_string_space', function() update_search_results(' ') end, 'repeatable')
+ mp.add_forced_key_binding('`', 'search_string_`', function() update_search_results('`') end, 'repeatable')
+ mp.add_forced_key_binding('~', 'search_string_~', function() update_search_results('~') end, 'repeatable')
+ mp.add_forced_key_binding('!', 'search_string_!', function() update_search_results('!') end, 'repeatable')
+ mp.add_forced_key_binding('@', 'search_string_@', function() update_search_results('@') end, 'repeatable')
+ mp.add_forced_key_binding('SHARP', 'search_string_sharp', function() update_search_results('#') end, 'repeatable')
+ mp.add_forced_key_binding('$', 'search_string_$', function() update_search_results('$') end, 'repeatable')
+ mp.add_forced_key_binding('%', 'search_string_percentage', function() update_search_results('%') end, 'repeatable')
+ mp.add_forced_key_binding('^', 'search_string_^', function() update_search_results('^') end, 'repeatable')
+ mp.add_forced_key_binding('&', 'search_string_&', function() update_search_results('&') end, 'repeatable')
+ mp.add_forced_key_binding('*', 'search_string_*', function() update_search_results('*') end, 'repeatable')
+ mp.add_forced_key_binding('(', 'search_string_(', function() update_search_results('(') end, 'repeatable')
+ mp.add_forced_key_binding(')', 'search_string_)', function() update_search_results(')') end, 'repeatable')
+ mp.add_forced_key_binding('-', 'search_string_-', function() update_search_results('-') end, 'repeatable')
+ mp.add_forced_key_binding('_', 'search_string__', function() update_search_results('_') end, 'repeatable')
+ mp.add_forced_key_binding('=', 'search_string_=', function() update_search_results('=') end, 'repeatable')
+ mp.add_forced_key_binding('+', 'search_string_+', function() update_search_results('+') end, 'repeatable')
+ mp.add_forced_key_binding('\\', 'search_string_\\', function() update_search_results('\\') end, 'repeatable')
+ mp.add_forced_key_binding('|', 'search_string_|', function() update_search_results('|') end, 'repeatable')
+ mp.add_forced_key_binding(']', 'search_string_]', function() update_search_results(']') end, 'repeatable')
+ mp.add_forced_key_binding('}', 'search_string_rightcurly', function() update_search_results('}') end, 'repeatable')
+ mp.add_forced_key_binding('[', 'search_string_[', function() update_search_results('[') end, 'repeatable')
+ mp.add_forced_key_binding('{', 'search_string_leftcurly', function() update_search_results('{') end, 'repeatable')
+ mp.add_forced_key_binding('\'', 'search_string_\'', function() update_search_results('\'') end, 'repeatable')
+ mp.add_forced_key_binding('\"', 'search_string_\"', function() update_search_results('\"') end, 'repeatable')
+ mp.add_forced_key_binding(';', 'search_string_semicolon', function() update_search_results(';') end, 'repeatable')
+ mp.add_forced_key_binding(':', 'search_string_:', function() update_search_results(':') end, 'repeatable')
+ mp.add_forced_key_binding('/', 'search_string_/', function() update_search_results('/') end, 'repeatable')
+ mp.add_forced_key_binding('?', 'search_string_?', function() update_search_results('?') end, 'repeatable')
+ mp.add_forced_key_binding('.', 'search_string_.', function() update_search_results('.') end, 'repeatable')
+ mp.add_forced_key_binding('>', 'search_string_>', function() update_search_results('>') end, 'repeatable')
+ mp.add_forced_key_binding(',', 'search_string_,', function() update_search_results(',') end, 'repeatable')
+ mp.add_forced_key_binding('<', 'search_string_<', function() update_search_results('<') end, 'repeatable')
+
+ mp.add_forced_key_binding('bs', 'search_string_del', function() update_search_results('', 'string_del') end, 'repeatable')
+ bind_keys(o.list_close_keybind, 'search_exit', function() list_search_exit() end)
+ bind_keys(o.list_search_not_typing_mode_keybind, 'search_string_not_typing', function()list_search_not_typing_mode(false) end)
+
+ if o.search_not_typing_smartly then
+ bind_keys(o.next_filter_sequence_keybind, 'list-filter-next', function() list_filter_next() list_search_not_typing_mode(true) end)
+ bind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous', function() list_filter_previous() list_search_not_typing_mode(true) end)
+ bind_keys(o.list_delete_keybind, 'list-delete', function() list_delete() list_search_not_typing_mode(true) end)
+ bind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight', function() list_delete('highlight') list_search_not_typing_mode(true) end)
+ bind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove', function() slot_remove() list_search_not_typing_mode(true) end)
+ bind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove-highlight', function() slot_remove('highlight') list_search_not_typing_mode(true) end)
+ end
+end
+
+function unbind_search_keys()
+ mp.remove_key_binding('search_string_a')
+ mp.remove_key_binding('search_string_b')
+ mp.remove_key_binding('search_string_c')
+ mp.remove_key_binding('search_string_d')
+ mp.remove_key_binding('search_string_e')
+ mp.remove_key_binding('search_string_f')
+ mp.remove_key_binding('search_string_g')
+ mp.remove_key_binding('search_string_h')
+ mp.remove_key_binding('search_string_i')
+ mp.remove_key_binding('search_string_j')
+ mp.remove_key_binding('search_string_k')
+ mp.remove_key_binding('search_string_l')
+ mp.remove_key_binding('search_string_m')
+ mp.remove_key_binding('search_string_n')
+ mp.remove_key_binding('search_string_o')
+ mp.remove_key_binding('search_string_p')
+ mp.remove_key_binding('search_string_q')
+ mp.remove_key_binding('search_string_r')
+ mp.remove_key_binding('search_string_s')
+ mp.remove_key_binding('search_string_t')
+ mp.remove_key_binding('search_string_u')
+ mp.remove_key_binding('search_string_v')
+ mp.remove_key_binding('search_string_w')
+ mp.remove_key_binding('search_string_x')
+ mp.remove_key_binding('search_string_y')
+ mp.remove_key_binding('search_string_z')
+
+ mp.remove_key_binding('search_string_A')
+ mp.remove_key_binding('search_string_B')
+ mp.remove_key_binding('search_string_C')
+ mp.remove_key_binding('search_string_D')
+ mp.remove_key_binding('search_string_E')
+ mp.remove_key_binding('search_string_F')
+ mp.remove_key_binding('search_string_G')
+ mp.remove_key_binding('search_string_H')
+ mp.remove_key_binding('search_string_I')
+ mp.remove_key_binding('search_string_J')
+ mp.remove_key_binding('search_string_K')
+ mp.remove_key_binding('search_string_L')
+ mp.remove_key_binding('search_string_M')
+ mp.remove_key_binding('search_string_N')
+ mp.remove_key_binding('search_string_O')
+ mp.remove_key_binding('search_string_P')
+ mp.remove_key_binding('search_string_Q')
+ mp.remove_key_binding('search_string_R')
+ mp.remove_key_binding('search_string_S')
+ mp.remove_key_binding('search_string_T')
+ mp.remove_key_binding('search_string_U')
+ mp.remove_key_binding('search_string_V')
+ mp.remove_key_binding('search_string_W')
+ mp.remove_key_binding('search_string_X')
+ mp.remove_key_binding('search_string_Y')
+ mp.remove_key_binding('search_string_Z')
+
+ mp.remove_key_binding('search_string_1')
+ mp.remove_key_binding('search_string_2')
+ mp.remove_key_binding('search_string_3')
+ mp.remove_key_binding('search_string_4')
+ mp.remove_key_binding('search_string_5')
+ mp.remove_key_binding('search_string_6')
+ mp.remove_key_binding('search_string_7')
+ mp.remove_key_binding('search_string_8')
+ mp.remove_key_binding('search_string_9')
+ mp.remove_key_binding('search_string_0')
+
+ mp.remove_key_binding('search_string_space')
+ mp.remove_key_binding('search_string_`')
+ mp.remove_key_binding('search_string_~')
+ mp.remove_key_binding('search_string_!')
+ mp.remove_key_binding('search_string_@')
+ mp.remove_key_binding('search_string_sharp')
+ mp.remove_key_binding('search_string_$')
+ mp.remove_key_binding('search_string_percentage')
+ mp.remove_key_binding('search_string_^')
+ mp.remove_key_binding('search_string_&')
+ mp.remove_key_binding('search_string_*')
+ mp.remove_key_binding('search_string_(')
+ mp.remove_key_binding('search_string_)')
+ mp.remove_key_binding('search_string_-')
+ mp.remove_key_binding('search_string__')
+ mp.remove_key_binding('search_string_=')
+ mp.remove_key_binding('search_string_+')
+ mp.remove_key_binding('search_string_\\')
+ mp.remove_key_binding('search_string_|')
+ mp.remove_key_binding('search_string_]')
+ mp.remove_key_binding('search_string_rightcurly')
+ mp.remove_key_binding('search_string_[')
+ mp.remove_key_binding('search_string_leftcurly')
+ mp.remove_key_binding('search_string_\'')
+ mp.remove_key_binding('search_string_\"')
+ mp.remove_key_binding('search_string_semicolon')
+ mp.remove_key_binding('search_string_:')
+ mp.remove_key_binding('search_string_/')
+ mp.remove_key_binding('search_string_?')
+ mp.remove_key_binding('search_string_.')
+ mp.remove_key_binding('search_string_>')
+ mp.remove_key_binding('search_string_,')
+ mp.remove_key_binding('search_string_<')
+
+ mp.remove_key_binding('search_string_del')
+ if not search_active then
+ unbind_keys(o.list_close_keybind, 'search_exit')
+ end
+end
+--End of LogManager Search Feature--
+---------End of LogManager---------
+
+--Modify Additional Log Parameters--
+function remove_all_additional_param_log_entry(index, log_text)
+ if not index or not log_text then return end
+ local temp_log_contents = read_log_table()
+ if not temp_log_contents or not temp_log_contents[1] then return end
+
+ for i = #temp_log_contents, 1, -1 do
+ if temp_log_contents[i].found_line:find(log_text..index) then
+ temp_log_contents[i].found_line = string.gsub(temp_log_contents[i].found_line, ' | '..log_text..index, "")
+ end
+ end
+
+ f = io.open(log_fullpath, "w+")
+ if temp_log_contents ~= nil and temp_log_contents[1] then
+ for i = 1, #temp_log_contents do
+ f:write(("%s\n"):format(temp_log_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+
+function remove_additional_param_log_entry(index, target, log_text)
+ if not index or not target or not log_text then return msg.error('remove_additional_param_log_entry parameters not defined') end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ local temp_log_contents = read_log_table()
+ if not temp_log_contents or not temp_log_contents[1] then return end
+
+ local log_index = osd_log_contents[target].found_sequence
+
+ if temp_log_contents[log_index].found_line:find(log_text..index) then
+ temp_log_contents[log_index].found_line = string.gsub(temp_log_contents[log_index].found_line, ' | '..log_text..index, "")
+ else
+ return msg.error('temp_log_contents[log_index].found_line is not found')
+ end
+
+ f = io.open(log_fullpath, "w+")
+ if temp_log_contents ~= nil and temp_log_contents[1] then
+ for i = 1, #temp_log_contents do
+ f:write(("%s\n"):format(temp_log_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+
+function add_additional_param_log_entry(index, target, log_text)
+ if not index or not target or not log_text then return msg.error('add_additional_param_log_entry parameters not defined') end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ local temp_log_contents = read_log_table()
+ if not temp_log_contents or not temp_log_contents[1] then return end
+ local log_index = osd_log_contents[target].found_sequence
+
+ if temp_log_contents[log_index].found_line then
+ if temp_log_contents[log_index].found_line:sub(-1) ~= ' ' then
+ temp_log_contents[log_index].found_line = temp_log_contents[log_index].found_line..' | '..log_text .. index..' | '
+ else
+ temp_log_contents[log_index].found_line = temp_log_contents[log_index].found_line..log_text .. index..' | '
+ end
+ else
+ return msg.error('temp_log_contents[log_index].found_line is not found')
+ end
+
+ f = io.open(log_fullpath, "w+")
+ if temp_log_contents ~= nil and temp_log_contents[1] then
+ for i = 1, #temp_log_contents do
+ f:write(("%s\n"):format(temp_log_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+--End Of Modify Additional Log Parameters--
+
+--Keybind Slot Feature--
+function list_slot_remove(index, action)
+ if not list_drawn then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ if not index then index = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_slot) end
+
+ if not index then
+ if action ~= 'silent' then msg.info("Failed to remove") end
+ return
+ end
+ remove_all_additional_param_log_entry(index, log_keybind_text)
+ if action ~= 'silent' then msg.info('Removed Keybind: ' .. get_slot_keybind(index)) end
+end
+
+function list_slot_remove_highlighted()
+ if not list_drawn then return end
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ local slotIndex
+ for i = 1, #osd_log_contents do
+ for j=1, #list_highlight_cursor do
+ if osd_log_contents[#osd_log_contents+1-i] then
+ if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then
+ slotIndex = tonumber(osd_log_contents[#osd_log_contents+1-i].found_slot)
+ if slotIndex then
+ remove_all_additional_param_log_entry(slotIndex, log_keybind_text)
+ msg.info('Removed Keybind: ' .. get_slot_keybind(slotIndex))
+ end
+ end
+ end
+ end
+ end
+end
+
+function list_slot_add(index)
+ if not list_drawn then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ if not index then return end
+
+ local cursor_filename, cursor_filepath, cursor_filetitle = get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.2.4# added the new name calling method to fix issue of unable to add urls into groups or slots
+ local cursor_seektime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time)
+ if not cursor_filename or not cursor_seektime then
+ msg.info("Failed to add slot")
+ return
+ end
+
+
+ local slotIndex = osd_log_contents[#osd_log_contents - list_cursor + 1].found_slot
+ if slotIndex then
+ remove_additional_param_log_entry(slotIndex,#osd_log_contents-list_cursor+1, log_keybind_text)
+ end
+
+ list_slot_remove(index, 'silent')
+ add_additional_param_log_entry(index, #osd_log_contents-list_cursor+1, log_keybind_text)
+ msg.info('Added Keybind:\n' .. cursor_filetitle .. ' 🕒 ' .. format_time(cursor_seektime) .. ' ⌨ ' .. get_slot_keybind(index))
+end
+
+function slot_remove(action)
+ if not action then
+ list_slot_remove()
+ elseif action == 'highlight' then
+ list_slot_remove_highlighted()
+ end
+ get_osd_log_contents()
+ if #osd_log_contents == 0 then
+ display_list('all')
+ return
+ elseif list_cursor ~= #osd_log_contents + 1 then
+ select(0)
+ else
+ select(-1)
+ end
+end
+
+function slot_add(index)
+ if not index then return end
+
+ list_slot_add(index)
+ get_osd_log_contents()
+ if #osd_log_contents == 0 then
+ list_cursor = 0
+ select(list_cursor)
+ elseif list_cursor ~= #osd_log_contents + 1 then
+ select(0)
+ else
+ select(-1)
+ end
+end
+--End of Keybind Slot Feature--
+
+--Group Feature--
+function list_group_remove(action)
+ if not list_drawn then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ local groupCursorIndex = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_group)
+ if not groupCursorIndex then
+ if action ~= 'silent' then msg.info("Failed to remove") end
+ return
+ end
+ remove_additional_param_log_entry(groupCursorIndex, #osd_log_contents-list_cursor+1, log_group_text)
+ if action ~= 'silent' then msg.info('Removed Group: ' .. get_group_properties(groupCursorIndex).name) end
+end
+
+function list_group_remove_highlighted()
+ if not list_drawn then return end
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ local groupIndex
+ for i = 1, #osd_log_contents do
+ for j=1, #list_highlight_cursor do
+ if osd_log_contents[#osd_log_contents+1-i] then
+ if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then
+ groupIndex = tonumber(osd_log_contents[#osd_log_contents+1-i].found_group)
+ if groupIndex then
+ remove_additional_param_log_entry(groupIndex, #osd_log_contents+1-i, log_group_text)
+ msg.info('Removed Group: ' .. get_group_properties(groupIndex).name)
+ end
+ end
+ end
+ end
+ end
+end
+
+function list_group_add(index)
+ if not list_drawn then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ if not index then return end
+
+ local cursor_filename, cursor_filepath, cursor_filetitle = get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.2.4# added the new name calling method to fix issue of unable to add urls into groups or slots
+ local cursor_seektime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time)
+ if not cursor_filename or not cursor_seektime then
+ msg.info("Failed to add group")
+ return
+ end
+
+ list_group_remove('silent')
+ add_additional_param_log_entry(index, #osd_log_contents-list_cursor+1, log_group_text)
+ msg.info('Added Group:\n' .. cursor_filename .. ' 🕒 ' .. format_time(cursor_seektime) .. ' 🖿 ' .. get_group_properties(index).name)
+end
+
+function list_group_add_highlighted(index)
+ if not list_drawn then return end
+ if not list_highlight_cursor or not list_highlight_cursor[1] then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ if not index then return end
+ list_group_remove_highlighted()
+
+ for i = 1, #osd_log_contents do
+ for j=1, #list_highlight_cursor do
+ if osd_log_contents[#osd_log_contents+1-i] then
+ if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then
+ add_additional_param_log_entry(index, #osd_log_contents+1-i, log_group_text)
+ msg.info('Added Group: ' .. get_group_properties(index).name)
+ end
+ end
+ end
+ end
+end
+
+function list_group_add_cycle(action)
+ if not list_drawn then return end
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ local next_index = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_group)
+ if next_index then next_index = next_index + 1 else next_index = 0 end
+ if next_index > #o.groups_list_and_keybind or next_index == 0 then
+ next_index = 1
+ end
+
+ if not action then
+ group_add(next_index)
+ elseif action == 'highlight' then
+ group_add(next_index, action)
+ end
+end
+
+function group_remove(action)
+ if not action then
+ list_group_remove()
+ elseif action == 'highlight' then
+ list_group_remove_highlighted()
+ end
+ get_osd_log_contents()
+ if #osd_log_contents == 0 then
+ display_list('all')
+ return
+ elseif list_cursor ~= #osd_log_contents + 1 then
+ select(0)
+ else
+ select(-1)
+ end
+end
+
+function group_add(index, action)
+ if not index then return end
+
+ if not action then
+ list_group_add(index)
+ elseif action == 'highlight' then
+ list_group_add_highlighted(index)
+ end
+ get_osd_log_contents()
+ if #osd_log_contents == 0 then
+ list_cursor = 0
+ select(list_cursor)
+ elseif list_cursor ~= #osd_log_contents + 1 then
+ select(0)
+ else
+ select(-1)
+ end
+end
+--End of Group Feature--
+
+function mark_chapter()
+ if not o.mark_bookmark_as_chapter then return end
+
+ local all_chapters = mp.get_property_native("chapter-list")
+ local chapter_index = 0
+ local chapters_time = {}
+
+ get_osd_log_contents()
+ if not osd_log_contents or not osd_log_contents[1] then return end
+ for i = 1, #osd_log_contents do
+ if osd_log_contents[i].found_path == filePath and tonumber(osd_log_contents[i].found_time) > 0 then
+ table.insert(chapters_time, tonumber(osd_log_contents[i].found_time))
+ end
+ end
+ if not chapters_time[1] then return end
+
+ table.sort(chapters_time, function(a, b) return a < b end)
+
+ for i = 1, #chapters_time do
+ chapter_index = chapter_index + 1
+
+ all_chapters[chapter_index] = {
+ title = 'SimpleBookmark ' .. chapter_index,
+ time = chapters_time[i]
+ }
+ end
+
+ table.sort(all_chapters, function(a, b) return a['time'] < b['time'] end)
+
+ mp.set_property_native("chapter-list", all_chapters)
+end
+
+function write_log(target_time, update_seekTime, entry_limit)
+ if not filePath then return end
+ if o.preserve_video_settings then mp.command("write-watch-later-config") end--1.3.1# option to preserve video settings by using write-watch-later-config when saving bookmark #84
+
+ local prev_seekTime = seekTime
+ local deleted_entries = {} --1.2.7# add it above since we need to call it later for preserving properties
+
+ seekTime = (mp.get_property_number('time-pos') or 0)
+ if target_time then
+ seekTime = target_time
+ end
+ if seekTime < 0 then seekTime = 0 end
+
+ local found_entry = find_entry(true, filePath, math.floor(seekTime)) --1.2.5# finds entry_sequence using new function --1.2.6# updated to find_entry
+ --1.2.8# first delete_log_entry to correctly overwrite the data (having same_path_log_delete() function runs first will result in overwriting wrong entry)
+ if found_entry and found_entry['found_sequence'] ~= nil then --1.2.6# if the entry exists then proceed to delete it
+ delete_log_entry(found_entry['found_sequence']) --1.2.5# deletes log entry using new function that uses sequence to delete --1.2.8# removed calling the array earlier and automatically call inside function
+ end
+ deleted_entries = same_path_log_delete(filePath, entry_limit) --1.2.5# seperate function to delete any additional entries based on the same_entry_limit set by user --1.2.7# assign it to varible since function now returns status and an array of deleted_entries --1.2.8# removed calling the array earlier and automatically call inside function
+
+ local f = io.open(log_fullpath, "a+")--1.3# dont allow customization to date_format so it can be saved in a standard in which I can parse for search results, etc..
+ if o.file_title_logging == 'all' then
+ f:write(("[%s] \"%s\" | %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), fileTitle, filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime)))
+ elseif o.file_title_logging == 'protocols' and (starts_protocol(o.logging_protocols, filePath)) or o.file_title_logging == 'local' and not (starts_protocol(o.logging_protocols, filePath)) then --1.3# added file_title_logging for local
+ f:write(("[%s] \"%s\" | %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), fileTitle, filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime)))
+ elseif o.file_title_logging == 'protocols' and not (starts_protocol(o.logging_protocols, filePath)) then
+ f:write(("[%s] %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime)))
+ else
+ f:write(("[%s] %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime)))
+ end
+
+ f:write('\n')
+ f:close()
+
+
+ --1.2.6# restore properties if o.overwrite_preserve_properties is enabled
+ if found_entry and o.overwrite_preserve_properties then
+ local temp_log_contents = read_log_table() --1.2.6# loop through table with the new additions
+ if not temp_log_contents or not temp_log_contents[1] then return end
+ --1.2.6# when a slot or group was found previously, then add it
+ if found_entry['found_slot'] then
+ remove_all_additional_param_log_entry(found_entry['found_slot'], log_keybind_text) --1.2.9# replaced list_slot_remove with remove_all_.. function to avoid possible errors since list_slot_remove has a check for list_drawn
+ add_additional_param_log_entry(found_entry['found_slot'], #temp_log_contents, log_keybind_text)
+ end
+ if found_entry['found_group'] then
+ add_additional_param_log_entry(found_entry['found_group'], #temp_log_contents, log_group_text)
+ end
+ end
+
+ --1.2.7# if an exact match is not found, and there are multiple deleted entries because of same_path_log_delete then add the latest deleted property to the newly added entry
+ if not found_entry and deleted_entries ~= nil and deleted_entries[1] and o.overwrite_preserve_properties then
+ local temp_log_contents = read_log_table() --1.2.6# loop through table with the new additions
+ if not temp_log_contents or not temp_log_contents[1] then return end
+ --1.2.7# loop through all deleted entries and get the first found slot and group then append it to the latest entry and then break loop
+ local break_table = false
+ for i = 1, #deleted_entries do
+ if deleted_entries[i] then
+ if deleted_entries[i].found_slot then
+ remove_all_additional_param_log_entry(deleted_entries[i].found_slot, log_keybind_text) --1.2.9# replaced list_slot_remove with remove_all_.. function to avoid possible errors since list_slot_remove has a check for list_drawn
+ add_additional_param_log_entry(deleted_entries[i].found_slot, #temp_log_contents, log_keybind_text)
+ break_table = true --1.2.7# break the table after addition is added since no need to continue looking for more
+ end
+ if deleted_entries[i].found_group then
+ add_additional_param_log_entry(deleted_entries[i].found_group, #temp_log_contents, log_group_text)
+ break_table = true --1.2.7# break the table after addition is added since no need to continue looking for more
+ end
+ if break_table then --1.2.7# if it found a slot and group or just slot or just a group then break the table
+ break
+ end
+ end
+ end
+ end
+
+ if not update_seekTime then
+ seekTime = prev_seekTime
+ end
+end
+
+function add_load_slot(key_index)
+ if not key_index then return end
+
+ local current_filePath = mp.get_property('path')
+ local list_filepath, list_filetitle, list_seektime
+ if list_drawn then
+ slot_add(key_index)
+ else
+ local slot_taken = false
+ get_osd_log_contents()
+ if osd_log_contents ~= nil and osd_log_contents[1] then
+ for i = 1, #osd_log_contents do
+ if tonumber(osd_log_contents[i].found_slot) == key_index then
+ list_filepath = osd_log_contents[i].found_path
+ list_filetitle = osd_log_contents[i].found_name
+ list_seektime = tonumber(osd_log_contents[i].found_time)
+ slot_taken = true
+ break
+ end
+ end
+ if slot_taken then
+ if file_exists(list_filepath) or starts_protocol(protocols, list_filepath) then
+ if list_filepath ~= current_filePath then
+ if o.preserve_video_settings then mp.command("write-watch-later-config") end--1.3.1# option to preserve video settings by using write-watch-later-config when loading bookmark replaces current file #84
+ mp.commandv('loadfile', list_filepath)
+ if o.keybinds_auto_resume then
+ resume_selected = true
+ end
+ elseif list_filepath == current_filePath and o.keybinds_auto_resume then
+ mp.commandv('seek', list_seektime, 'absolute', 'exact')
+ list_close_and_trash_collection()
+ elseif list_filepath == current_filePath and not o.keybinds_auto_resume then
+ mp.commandv('seek', 0, 'absolute', 'exact')
+ list_close_and_trash_collection()
+ end
+ if o.keybinds_auto_resume then
+ if o.osd_messages == true then
+ mp.osd_message('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle .. ' 🕒 ' .. format_time(list_seektime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
+ end
+ msg.info('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle .. ' 🕒 ' .. format_time(list_seektime))
+ else
+ if o.osd_messages == true then
+ mp.osd_message('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle)
+ end
+ msg.info('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle)
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message('File Doesn\'t Exist:\n' .. list_filepath)
+ end
+ msg.info('The file below doesn\'t seem to exist:\n' .. list_filepath)
+ return
+ end
+ else
+ if o.keybinds_empty_auto_create then
+ if filePath ~= nil then
+ if o.keybinds_empty_fileonly then
+ write_log(0) --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code
+ get_osd_log_contents() --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code -- also used get_osd instead of local because add_additional_param uses osd_log inside its function perhaps this should be changed
+ local current_slot = tonumber(osd_log_contents[#osd_log_contents].found_slot) --1.2.9# gets the slot of the current item
+ remove_all_additional_param_log_entry(current_slot, log_keybind_text) --1.2.9# removes all the slots of the current item
+ remove_all_additional_param_log_entry(key_index, log_keybind_text) --1.2.9# removes all the slots that are going to be added based on passed index
+ add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index
+ else
+ write_log(false) --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code
+ get_osd_log_contents() --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code
+ local current_slot = tonumber(osd_log_contents[#osd_log_contents].found_slot) --1.2.9# gets the slot of the current item
+ remove_all_additional_param_log_entry(current_slot, log_keybind_text) --1.2.9# removes all the slots of the current item
+ remove_all_additional_param_log_entry(key_index, log_keybind_text) --1.2.9# removes all the slots that are going to be added based on passed index
+ add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index
+ end
+ if o.osd_messages == true then
+ mp.osd_message('Bookmarked & Added Keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(mp.get_property_number('time-pos'), o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) .. ' ⌨ ' .. get_slot_keybind(key_index))
+ end
+ msg.info('Bookmarked the below & added keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(mp.get_property_number('time-pos')) .. ' ⌨ ' .. get_slot_keybind(key_index))
+ else
+ if o.osd_messages == true then
+ mp.osd_message('Failed to Bookmark & Auto Create Keybind\nNo Video Found')
+ end
+ msg.info("Failed to bookmark & auto create keybind, no video found")
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message('No Bookmark Slot For' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' Yet')
+ end
+ msg.info('No bookmark slot has been assigned for' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' keybind yet')
+ end
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message('No Bookmark Slot For' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' Yet')
+ end
+ msg.info('No bookmark slot has been assigned for' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' keybind yet')
+ end
+ end
+end
+
+function quicksave_slot(key_index)
+ if not key_index then return end
+
+ if list_drawn then
+ slot_add(key_index)
+ else
+ if filePath ~= nil then
+ if o.keybinds_quicksave_fileonly then
+ write_log(0) --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code -- also used get_osd instead of local because add_additional_param uses osd_log inside its function perhaps this should be changed
+ get_osd_log_contents() --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code
+ local current_slot = tonumber(osd_log_contents[#osd_log_contents].found_slot) --1.2.9# gets the slot of the current item
+ remove_all_additional_param_log_entry(current_slot, log_keybind_text) --1.2.9# removes all the slots of the current item
+ remove_all_additional_param_log_entry(key_index, log_keybind_text) --1.2.9# removes all the slots that are going to be added based on passed index
+ add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index
+
+ if o.osd_messages == true then
+ mp.osd_message('Bookmarked Fileonly & Added Keybind:\n' .. fileTitle .. ' ⌨ ' .. get_slot_keybind(key_index))
+ end
+ msg.info('Bookmarked the below & added keybind:\n' .. fileTitle .. ' ⌨ ' .. get_slot_keybind(key_index))
+ else
+ write_log(false, true) --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code
+ get_osd_log_contents() --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code
+ local current_slot = tonumber(osd_log_contents[#osd_log_contents].found_slot) --1.2.9# gets the slot of the current item
+ remove_all_additional_param_log_entry(current_slot, log_keybind_text) --1.2.9# removes all the slots of the current item
+ remove_all_additional_param_log_entry(key_index, log_keybind_text) --1.2.9# removes all the slots that are going to be added based on passed index
+ add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index
+
+ if o.osd_messages == true then
+ mp.osd_message('Bookmarked & Added Keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) .. ' ⌨ ' .. get_slot_keybind(key_index))
+ end
+ msg.info('Bookmarked the below & added keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime) .. ' ⌨ ' .. get_slot_keybind(key_index))
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message('Failed to Bookmark & Auto Create Keybind\nNo Video Found')
+ end
+ msg.info("Failed to bookmark & auto create keybind, no video found")
+ end
+ end
+end
+
+function bookmark_save()
+ if filePath ~= nil then
+ write_log(false, true, o.same_entry_limit) --1.2.9# reflect removal of key_index in write_log function
+ if list_drawn then
+ get_osd_log_contents()
+ select(0)
+ end
+ if o.osd_messages == true then
+ mp.osd_message('Bookmarked:\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
+ end
+ msg.info('Added the below to bookmarks\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime))
+ elseif filePath == nil and o.bookmark_loads_last_idle then
+ osd_log_contents = read_log_table()
+ load(1)
+ else
+ if o.osd_messages == true then
+ mp.osd_message('Failed to Bookmark\nNo Video Found')
+ end
+ msg.info("Failed to bookmark, no video found")
+ end
+end
+
+function bookmark_fileonly_save()
+ if filePath ~= nil then
+ write_log(0, false, o.same_entry_limit) --1.2.9# reflect removal of key_index in write_log function
+ if list_drawn then
+ get_osd_log_contents()
+ select(0)
+ end
+ if o.osd_messages == true then
+ mp.osd_message('Bookmarked File Only:\n' .. fileTitle)
+ end
+ msg.info('Added the below to bookmarks\n' .. fileTitle)
+ elseif filePath == nil and o.bookmark_fileonly_loads_last_idle then
+ osd_log_contents = read_log_table()
+ load(1, false, 0)
+ else
+ if o.osd_messages == true then
+ mp.osd_message('Failed to Bookmark\nNo Video Found')
+ end
+ msg.info("Failed to bookmark, no video found")
+ end
+end
+
+mp.register_event('file-loaded', function()
+ list_close_and_trash_collection()
+ filePath, fileTitle, fileLength = get_file()
+ loadTriggered = true --1.1.5# for resume and resume-notime startup behavior (so that it only triggers if started as idle and only once)
+ if (resume_selected == true and seekTime ~= nil) then
+ mp.commandv('seek', seekTime, 'absolute', 'exact')
+ resume_selected = false
+ end
+ mark_chapter()
+end)
+
+mp.observe_property("idle-active", "bool", function(_, v)
+ if v then --1.3.0# if idle is triggered
+ filePath, fileTitle, fileLength = nil --1.3.0# set it back to nil if idle is triggered for better trash collection. issue #69
+ end
+
+ if v and o.auto_run_list_idle ~= 'none' then
+ display_list(o.auto_run_list_idle, nil, 'hide-osd')
+ end
+
+ if v and type(o.load_item_on_startup) == "number" and not loadTriggered then --1.3.0# option to immediately load an entry based on number
+ if o.load_item_on_startup == 0 then return end --1.3.0# if the entry loaded is 0 then exit this, this is automatically handled in load also but it is better to exit here since there will be a loop below this
+
+ osd_log_contents = read_log_table() --1.3.0# get the item list to use load function
+ if not osd_log_contents or not osd_log_contents[1] then return end
+
+ if o.load_item_on_startup == -1 then o.load_item_on_startup = #osd_log_contents end --1.3.0# specify -1 as last entry
+ load(o.load_item_on_startup)
+ end
+end)
+
+bind_keys(o.bookmark_save_keybind, 'bookmark-save', bookmark_save)
+bind_keys(o.bookmark_fileonly_keybind, 'bookmark-fileonly', bookmark_fileonly_save)
+
+for i = 1, #o.open_list_keybind do
+ if i == 1 then
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list', function()display_list(o.open_list_keybind[i][2]) end)
+ else
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list'..i, function()display_list(o.open_list_keybind[i][2]) end)
+ end
+end
+
+for i = 1, #o.keybinds_add_load_keybind do
+ mp.add_forced_key_binding(o.keybinds_add_load_keybind[i], 'keybind-slot-' .. i, function()add_load_slot(i) end)
+end
+
+for i = 1, #o.keybinds_quicksave_keybind do
+ mp.add_forced_key_binding(o.keybinds_quicksave_keybind[i], 'keybind-slot-save-' .. i, function()quicksave_slot(i) end)
+end
diff --git a/ar/.config/mpv/scripts/SmartCopyPaste_II.lua b/ar/.config/mpv/scripts/SmartCopyPaste_II.lua
new file mode 100644
index 0000000..ca5bf58
--- /dev/null
+++ b/ar/.config/mpv/scripts/SmartCopyPaste_II.lua
@@ -0,0 +1,3797 @@
+-- Copyright (c) 2022, Eisa AlAwadhi
+-- License: BSD 2-Clause License
+-- Creator: Eisa AlAwadhi
+-- Project: SmartCopyPaste_II
+-- Version: 3.2.1
+
+local o = {
+ ---------------------------USER CUSTOMIZATION SETTINGS---------------------------
+ --These settings are for users to manually change some options.
+ --Changes are recommended to be made in the script-opts directory.
+
+ -----Script Settings----
+ device = "auto", --'auto' is for automatic device detection, or manually change to: 'windows' or 'mac' or 'linux'
+ linux_copy = "xclip -silent -selection clipboard -in", --copy command that will be used in Linux. OR write a different command
+ linux_paste = "xclip -selection clipboard -o", --paste command that will be used in Linux. OR write a different command
+ mac_copy = "pbcopy", --copy command that will be used in MAC. OR write a different command
+ mac_paste = "pbpaste", --paste command that will be used in MAC. OR write a different command
+ windows_copy = "powershell", --'powershell' is for using windows powershell to copy. OR write the copy command, e.g: ' clip'
+ windows_paste = "powershell", --'powershell' is for using windows powershell to paste. OR write the paste command
+ auto_run_list_idle = "none", --Auto run the list when opening mpv and there is no video / file loaded. 'none' for disabled. Or choose between: 'all', 'copy', 'paste', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
+ toggle_idlescreen = false, --hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off)
+ resume_offset = -0.65, --change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point
+ osd_messages = true, --true is for displaying osd messages when actions occur. Change to false will disable all osd messages generated from this script
+ mark_clipboard_as_chapter = false, --true is for marking the time as a chapter. false disables mark as chapter behavior.
+ copy_time_method = "all", --Option to copy time with video, 'none' for disabled, 'all' to copy time for all videos, 'protocols' for copying time only for protocols, 'specifics' to copy time only for websites defined below, 'local' to copy time for videos that are not protocols
+ log_paste_idle_behavior = "force-noresume", --Behavior of paste when nothing valid is copied, and no video is running. select between 'force', 'force-noresume'
+ log_paste_running_behavior = "timestamp>playlist", --Behavior of paste when nothing valid is copied, and a video is running. select between 'timestamp>playlist', 'timestamp>force', 'timestamp', 'playlist', 'force', 'force-noresume'
+ specific_time_attributes = [[
+ [ ["twitter", "?t=", ""], ["twitch", "?t=", "s"], ["youtube", "&t=", "s"] ]
+ ]], --The time attributes which will be added when copying protocols of specific websites from this list. Additional attributes can be added following the same format.
+ protocols_time_attribute = "&t=", --The default text that will be copied before the seek time when copying a protocol video from mpv, specific_time_attributes takes priority
+ local_time_attribute = "&time=", --The text that will be copied before the seek time when copying a local video from mpv
+ pastable_time_attributes = [[
+ [" | time="]
+ ]], --The time attributes that can be pasted for resume, specific_time_attributes, protocols_time_attribute, local_time_attribute are automatically added
+ copy_keybind = [[
+ ["ctrl+y", "ctrl+Y", "meta+y", "meta+Y"]
+ ]], --Keybind that will be used to copy
+ running_paste_behavior = "playlist", --The priority of paste behavior when a video is running. select between 'playlist', 'timestamp', 'force'.
+ paste_keybind = [[
+ ["ctrl+p", "ctrl+P", "meta+p", "meta+P"]
+ ]], --Keybind that will be used to paste
+ copy_specific_behavior = "path", --Copy behavior when using copy_specific_keybind. select between 'title', 'path', 'timestamp', 'path&timestamp'.
+ copy_specific_keybind = [[
+ ["ctrl+alt+y", "ctrl+alt+Y", "meta+alt+y", "meta+alt+Y"]
+ ]], --Keybind that will be used to copy based on the copy behavior specified
+ paste_specific_behavior = "playlist", --Paste behavior when using paste_specific_keybind. select between 'playlist', 'timestamp', 'force'.
+ paste_specific_keybind = [[
+ ["ctrl+alt+p", "ctrl+alt+P", "meta+alt+p", "meta+alt+P"]
+ ]], --Keybind that will be used to paste based on the paste behavior specified
+ paste_protocols = [[
+ ["https?://", "magnet:", "rtmp:", "file:"]
+ ]], --add above (after a comma) any protocol you want paste to work with; e.g: ,'ftp://'. Or set it as "" by deleting all defined protocols to make paste works with any protocol.
+ paste_extensions = [[
+ ["ac3", "a52", "eac3", "mlp", "dts", "dts-hd", "dtshd", "true-hd", "thd", "truehd", "thd+ac3", "tta", "pcm", "wav", "aiff", "aif", "aifc", "amr", "awb", "au", "snd", "lpcm", "yuv", "y4m", "ape", "wv", "shn", "m2ts", "m2t", "mts", "mtv", "ts", "tsv", "tsa", "tts", "trp", "adts", "adt", "mpa", "m1a", "m2a", "mp1", "mp2", "mp3", "mpeg", "mpg", "mpe", "mpeg2", "m1v", "m2v", "mp2v", "mpv", "mpv2", "mod", "tod", "vob", "vro", "evob", "evo", "mpeg4", "m4v", "mp4", "mp4v", "mpg4", "m4a", "aac", "h264", "avc", "x264", "264", "hevc", "h265", "x265", "265", "flac", "oga", "ogg", "opus", "spx", "ogv", "ogm", "ogx", "mkv", "mk3d", "mka", "webm", "weba", "avi", "vfw", "divx", "3iv", "xvid", "nut", "flic", "fli", "flc", "nsv", "gxf", "mxf", "wma", "wm", "wmv", "asf", "dvr-ms", "dvr", "wtv", "dv", "hdv", "flv","f4v", "f4a", "qt", "mov", "hdmov", "rm", "rmvb", "ra", "ram", "3ga", "3ga2", "3gpp", "3gp", "3gp2", "3g2", "ay", "gbs", "gym", "hes", "kss", "nsf", "nsfe", "sap", "spc", "vgm", "vgz", "m3u", "m3u8", "pls", "cue",
+ "ase", "art", "bmp", "blp", "cd5", "cit", "cpt", "cr2", "cut", "dds", "dib", "djvu", "egt", "exif", "gif", "gpl", "grf", "icns", "ico", "iff", "jng", "jpeg", "jpg", "jfif", "jp2", "jps", "lbm", "max", "miff", "mng", "msp", "nitf", "ota", "pbm", "pc1", "pc2", "pc3", "pcf", "pcx", "pdn", "pgm", "PI1", "PI2", "PI3", "pict", "pct", "pnm", "pns", "ppm", "psb", "psd", "pdd", "psp", "px", "pxm", "pxr", "qfx", "raw", "rle", "sct", "sgi", "rgb", "int", "bw", "tga", "tiff", "tif", "vtf", "xbm", "xcf", "xpm", "3dv", "amf", "ai", "awg", "cgm", "cdr", "cmx", "dxf", "e2d", "egt", "eps", "fs", "gbr", "odg", "svg", "stl", "vrml", "x3d", "sxd", "v2d", "vnd", "wmf", "emf", "art", "xar", "png", "webp", "jxr", "hdp", "wdp", "cur", "ecw", "iff", "lbm", "liff", "nrrd", "pam", "pcx", "pgf", "sgi", "rgb", "rgba", "bw", "int", "inta", "sid", "ras", "sun", "tga",
+ "torrent"]
+ ]], --add above (after a comma) any extension you want paste to work with; e.g: ,'pdf'. Or set it as "" by deleting all defined extension to make paste works with any extension.
+ paste_subtitles = [[
+ ["aqt", "gsub", "jss", "sub", "ttxt", "pjs", "psb", "rt", "smi", "slt", "ssf", "srt", "ssa", "ass", "usf", "idx", "vtt"]
+ ]], --add above (after a comma) any extension you want paste to attempt to add as a subtitle file, e.g.:'txt'. Or set it as "" by deleting all defined extension to make paste attempt to add any subtitle.
+ open_list_keybind = [[
+ [ ["y", "all"], ["Y", "all"] ]
+ ]], --Keybind that will be used to open the list along with the specified filter. Available filters: 'all', 'copy', 'paste', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
+ list_filter_jump_keybind = [[
+ [ ["y", "all"], ["Y", "all"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ]
+ ]], --Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available fitlers: 'all', 'copy', 'paste', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'.
+
+ -----Logging Settings-----
+ log_path = "/:dir%mpvconf%", --Change to '/:dir%script%' for placing it in the same directory of script, OR change to '/:dir%mpvconf%' for mpv portable_config directory. OR write any variable using '/:var' then the variable '/:var%APPDATA%' you can use path also, such as: '/:var%APPDATA%\\mpv' OR '/:var%HOME%/mpv' OR specify the absolute path , e.g.: 'C:\\Users\\Eisa01\\Desktop\\'
+ log_file = "mpvClipboard.log", --name+extension of the file that will be used to store the log data
+ date_format = "%A/%B %d/%m/%Y %X", --Date format in the log (see lua date formatting), e.g.:'%d/%m/%y %X' or '%d/%b/%y %X'
+ file_title_logging = "protocols", --Change between 'all', 'protocols', 'none'. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone
+ logging_protocols = [[
+ ["https?://", "magnet:", "rtmp:"]
+ ]], --add above (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = 'protocols' or file_title_logging = 'all')
+ prefer_filename_over_title = "local", --Prefers to copy and log filename over filetitle. Select between 'local', 'protocols', 'all', and 'none'. 'local' prefer filenames for videos that are not protocols. 'protocols' will prefer filenames for protocols only. 'all' will prefer filename over filetitle for both protocols and not protocols videos. 'none' will always use filetitle instead of filename
+ same_entry_limit = 4, --Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry.
+
+ -----List Settings-----
+ loop_through_list = false, --true is for going up on the first item loops towards the last item and vise-versa. false disables this behavior.
+ list_middle_loader = true, --false is for more items to show, then u must reach the end. true is for new items to show after reaching the middle of list.
+ show_paths = false, --Show file paths instead of media-title
+ show_item_number = true, --Show the number of each item before displaying its name and values.
+ slice_longfilenames = false, --Change to true or false. Slices long filenames per the amount specified below
+ slice_longfilenames_amount = 55, --Amount for slicing long filenames
+ list_show_amount = 10, --Change maximum number to show items at once
+ quickselect_0to9_keybind = true, --Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work)
+ main_list_keybind_twice_exits = true, --Will exit the list when double tapping the main list, even if the list was accessed through a different filter.
+ search_not_typing_smartly = true, --To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter.
+ search_behavior = "any", --'specific' to find a match of either a date, title, path / url, time. 'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results).
+
+ -----Filter Settings------
+ --available filters: "all" to display all the items. Or "copy" to display copied items. Or "paste" to display pasted items. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file.
+ filters_and_sequence = [[
+ ["all", "copy", "paste", "recents", "distinct", "protocols", "playing", "fileonly", "titleonly", "keywords"]
+ ]], --Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed.
+ next_filter_sequence_keybind = [[
+ ["RIGHT", "MBTN_FORWARD"]
+ ]], --Keybind that will be used to go to the next available filter based on the filters_and_sequence
+ previous_filter_sequence_keybind = [[
+ ["LEFT", "MBTN_BACK"]
+ ]], --Keybind that will be used to go to the previous available filter based on the filters_and_sequence
+ loop_through_filters = true, --true is for bypassing the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa. false disables this behavior.
+ keywords_filter_list = [[
+ [""]
+ ]], --Create a filter out of your desired 'keywords', e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]
+
+ -----Sort Settings------
+ --available sort: 'added-asc' is for the newest added item to show first. Or 'added-desc' for the newest added to show last. Or 'alphanum-asc' is for A to Z approach with filename and episode number lower first. Or 'alphanum-desc' is for its Z to A approach. Or 'time-asc', 'time-desc' to sort the list based on time.
+ list_default_sort = "added-asc", --the default sorting method for all the different filters in the list. select between 'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'
+ list_filters_sort = [[
+ [ ]
+ ]], --Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ]
+ list_cycle_sort_keybind = [[
+ ["alt+s", "alt+S"]
+ ]], --Keybind to cycle through the different available sorts when list is open
+
+ -----List Design Settings-----
+ list_alignment = 7, --The alignment for the list, uses numpad positions choose from 1-9 or 0 to disable. e,g.:7 top left alignment, 8 top middle alignment, 9 top right alignment.
+ text_time_type = "duration", --The time type for items on the list. Select between 'duration', 'length', 'remaining'.
+ time_seperator = " 🕒 ", --Time seperator that will be used before the saved time
+ list_sliced_prefix = "...\\h\\N\\N", --The text that indicates there are more items above. \\h\\N\\N is for new line.
+ list_sliced_suffix = "...", --The text that indicates there are more items below.
+ quickselect_0to9_pre_text = false, --true enables pre text for showing quickselect keybinds before the list. false to disable
+ text_color = "ffffff", --Text color for list in BGR hexadecimal
+ text_scale = 50, --Font size for the text of list
+ text_border = 0.7, --Black border size for the text of list
+ text_cursor_color = "ffbf7f", --Highlight color in BGR hexadecimal
+ text_cursor_scale = 50, --Font size for highlighted text in list
+ text_cursor_border = 0.7, --Black border size for highlighted text in list
+ text_highlight_pre_text = "✅ ", --Pre text for highlighted multi-select item
+ search_color_typing = "ffffaa", --Search color when in typing mode
+ search_color_not_typing = "56ffaa", --Search color when not in typing mode and it is active
+ header_color = "56ffaa", --Header color in BGR hexadecimal
+ header_scale = 55, --Header text size for the list
+ header_border = 0.8, --Black border size for the Header of list
+ header_text = "📋 Clipboard [%cursor%/%total%]%prehighlight%%highlight%%afterhighlight%%prefilter%%filter%%afterfilter%%presort%%sort%%aftersort%%presearch%%search%%aftersearch%", --Text to be shown as header for the list
+ --Available header variables: %cursor%, %total%, %highlight%, %filter%, %search%, %listduration%, %listlength%, %listremaining%
+ --User defined text that only displays if a variable is triggered: %prefilter%, %afterfilter%, %prehighlight%, %afterhighlight% %presearch%, %aftersearch%, %prelistduration%, %afterlistduration%, %prelistlength%, %afterlistlength%, %prelistremaining%, %afterlistremaining%
+ --Variables explanation: %cursor: displays the number of cursor position in list. %total: total amount of items in current list. %highlight%: total number of highlighted items. %filter: shows the filter name, %search: shows the typed search. Example of user defined text that only displays if a variable is triggered of user: %prefilter: user defined text before showing filter, %afterfilter: user defined text after showing filter.
+ header_sort_hide_text = "added-asc", --Sort method that is hidden from header when using %sort% variable
+ header_sort_pre_text = " \\{", --Text to be shown before sort in the header, when using %presort%
+ header_sort_after_text = "}", --Text to be shown after sort in the header, when using %aftersort%
+ header_filter_pre_text = " [Filter: ", --Text to be shown before filter in the header, when using %prefilter%
+ header_filter_after_text = "]", --Text to be shown after filter in the header, when using %afterfilter%
+ header_search_pre_text = "\\h\\N\\N[Search=", --Text to be shown before search in the header, when using %presearch%
+ header_search_after_text = "..]", --Text to be shown after search in the header, when using %aftersearch%
+ header_highlight_pre_text = "✅", --Text to be shown before total highlighted items of displayed list in the header
+ header_highlight_after_text = "", --Text to be shown after total highlighted items of displayed list in the header
+ header_list_duration_pre_text = " 🕒 ", --Text to be shown before playback total duration of displayed list in the header
+ header_list_duration_after_text = "", --Text to be shown after playback total duration of displayed list in the header
+ header_list_length_pre_text = " 🕒 ", --Text to be shown before playback total duration of displayed list in the header
+ header_list_length_after_text = "", --Text to be shown after playback total duration of displayed list in the header
+ header_list_remaining_pre_text = " 🕒 ", --Text to be shown before playback total duration of displayed list in the header
+ header_list_remaining_after_text = "", --Text to be shown after playback total duration of displayed list in the header
+ copy_seperator = " ©", --Copy seperator that will be shown for copied items in the list
+ paste_seperator = " ℗", --Paste seperator that will be shown for pasted item in the list
+
+ -----Time Format Settings-----
+ --in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability).
+ --in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds.
+ --in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5]
+ copy_time_format = [[
+ ["timestamp-concise"]
+ ]],
+ osd_time_format = [[
+ ["default", "truncate"]
+ ]],
+ list_time_format = [[
+ ["default", "truncate"]
+ ]],
+ header_duration_time_format = [[
+ ["hms", "truncate", ":"]
+ ]],
+ header_length_time_format = [[
+ ["hms", "truncate", ":"]
+ ]],
+ header_remaining_time_format = [[
+ ["hms", "truncate", ":"]
+ ]],
+
+ -----List Keybind Settings-----
+ --Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind
+ --Example of changing and adding keybinds: --From ["b", "B"] To ["b"]. --From [""] to ["alt+b"]. --From [""] to ["a" "ctrl+a", "alt+a"]
+ list_move_up_keybind = [[
+ ["UP", "WHEEL_UP"]
+ ]], --Keybind that will be used to navigate up on the list
+ list_move_down_keybind = [[
+ ["DOWN", "WHEEL_DOWN"]
+ ]], --Keybind that will be used to navigate down on the list
+ list_page_up_keybind = [[
+ ["PGUP"]
+ ]], --Keybind that will be used to go to the first item for the page shown on the list
+ list_page_down_keybind = [[
+ ["PGDWN"]
+ ]], --Keybind that will be used to go to the last item for the page shown on the list
+ list_move_first_keybind = [[
+ ["HOME"]
+ ]], --Keybind that will be used to navigate to the first item on the list
+ list_move_last_keybind = [[
+ ["END"]
+ ]], --Keybind that will be used to navigate to the last item on the list
+ list_highlight_move_keybind = [[
+ ["SHIFT"]
+ ]], --Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc..
+ list_highlight_all_keybind = [[
+ ["ctrl+a", "ctrl+A"]
+ ]], --Keybind that will be used to highlight all displayed items on the list
+ list_unhighlight_all_keybind = [[
+ ["ctrl+d", "ctrl+D"]
+ ]], --Keybind that will be used to remove all currently highlighted items from the list
+ list_select_keybind = [[
+ ["ENTER", "MBTN_MID"]
+ ]], --Keybind that will be used to load entry based on cursor position
+ list_add_playlist_keybind = [[
+ ["CTRL+ENTER"]
+ ]], --Keybind that will be used to add entry to playlist based on cursor position
+ list_add_playlist_highlighted_keybind = [[
+ ["SHIFT+ENTER"]
+ ]], --Keybind that will be used to add all highlighted entries to playlist
+ list_close_keybind = [[
+ ["ESC", "MBTN_RIGHT"]
+ ]], --Keybind that will be used to close the list (closes search first if it is open)
+ list_delete_keybind = [[
+ ["DEL"]
+ ]], --Keybind that will be used to delete the entry based on cursor position
+ list_delete_highlighted_keybind = [[
+ ["SHIFT+DEL"]
+ ]], --Keybind that will be used to delete all highlighted entries from the list
+ list_search_activate_keybind = [[
+ ["ctrl+f", "ctrl+F"]
+ ]], --Keybind that will be used to trigger search
+ list_search_not_typing_mode_keybind = [[
+ ["ALT+ENTER"]
+ ]], --Keybind that will be used to exit typing mode of search while keeping search open
+ list_ignored_keybind = [[
+ ["h", "H", "r", "R", "b", "B", "k", "K"]
+ ]], --Keybind thats are ignored when list is open
+
+ ---------------------------END OF USER CUSTOMIZATION SETTINGS---------------------------
+};
+
+(require("mp.options")).read_options(o)
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+
+o.copy_keybind = utils.parse_json(o.copy_keybind)
+o.paste_keybind = utils.parse_json(o.paste_keybind)
+o.copy_specific_keybind = utils.parse_json(o.copy_specific_keybind)
+o.paste_specific_keybind = utils.parse_json(o.paste_specific_keybind)
+o.paste_protocols = utils.parse_json(o.paste_protocols)
+o.paste_extensions = utils.parse_json(o.paste_extensions)
+o.paste_subtitles = utils.parse_json(o.paste_subtitles)
+o.specific_time_attributes = utils.parse_json(o.specific_time_attributes)
+o.pastable_time_attributes = utils.parse_json(o.pastable_time_attributes)
+o.filters_and_sequence = utils.parse_json(o.filters_and_sequence)
+o.keywords_filter_list = utils.parse_json(o.keywords_filter_list)
+o.list_filters_sort = utils.parse_json(o.list_filters_sort)
+o.logging_protocols = utils.parse_json(o.logging_protocols)
+o.copy_time_format = utils.parse_json(o.copy_time_format)
+o.osd_time_format = utils.parse_json(o.osd_time_format)
+o.list_time_format = utils.parse_json(o.list_time_format)
+o.header_duration_time_format = utils.parse_json(o.header_duration_time_format)
+o.header_length_time_format = utils.parse_json(o.header_length_time_format)
+o.header_remaining_time_format = utils.parse_json(o.header_remaining_time_format)
+o.list_move_up_keybind = utils.parse_json(o.list_move_up_keybind)
+o.list_move_down_keybind = utils.parse_json(o.list_move_down_keybind)
+o.list_page_up_keybind = utils.parse_json(o.list_page_up_keybind)
+o.list_page_down_keybind = utils.parse_json(o.list_page_down_keybind)
+o.list_move_first_keybind = utils.parse_json(o.list_move_first_keybind)
+o.list_move_last_keybind = utils.parse_json(o.list_move_last_keybind)
+o.list_highlight_move_keybind = utils.parse_json(o.list_highlight_move_keybind)
+o.list_highlight_all_keybind = utils.parse_json(o.list_highlight_all_keybind)
+o.list_unhighlight_all_keybind = utils.parse_json(o.list_unhighlight_all_keybind)
+o.list_cycle_sort_keybind = utils.parse_json(o.list_cycle_sort_keybind)
+o.list_select_keybind = utils.parse_json(o.list_select_keybind)
+o.list_add_playlist_keybind = utils.parse_json(o.list_add_playlist_keybind)
+o.list_add_playlist_highlighted_keybind = utils.parse_json(o.list_add_playlist_highlighted_keybind)
+o.list_close_keybind = utils.parse_json(o.list_close_keybind)
+o.list_delete_keybind = utils.parse_json(o.list_delete_keybind)
+o.list_delete_highlighted_keybind = utils.parse_json(o.list_delete_highlighted_keybind)
+o.list_search_activate_keybind = utils.parse_json(o.list_search_activate_keybind)
+o.list_search_not_typing_mode_keybind = utils.parse_json(o.list_search_not_typing_mode_keybind)
+o.next_filter_sequence_keybind = utils.parse_json(o.next_filter_sequence_keybind)
+o.previous_filter_sequence_keybind = utils.parse_json(o.previous_filter_sequence_keybind)
+o.open_list_keybind = utils.parse_json(o.open_list_keybind)
+o.list_filter_jump_keybind = utils.parse_json(o.list_filter_jump_keybind)
+o.list_ignored_keybind = utils.parse_json(o.list_ignored_keybind)
+
+if utils.shared_script_property_set then
+ utils.shared_script_property_set("smartcopypaste-menu-open", "no")
+end
+mp.set_property("user-data/smartcopypaste/menu-open", "no")
+
+if string.lower(o.log_path) == "/:dir%mpvconf%" then
+ o.log_path = mp.find_config_file(".")
+elseif string.lower(o.log_path) == "/:dir%script%" then
+ o.log_path = debug.getinfo(1).source:match("@?(.*/)")
+elseif o.log_path:match("/:var%%(.*)%%") then
+ local os_variable = o.log_path:match("/:var%%(.*)%%")
+ o.log_path = o.log_path:gsub("/:var%%(.*)%%", os.getenv(os_variable))
+end
+local log_fullpath = utils.join_path(o.log_path, o.log_file)
+
+local log_length_text = "length="
+local log_time_text = "time="
+local log_clipboard_text = "clip="
+local protocols = { "https?:", "magnet:", "rtmps?:", "smb:", "ftps?:", "sftp:" }
+local available_filters = {
+ "all",
+ "copy",
+ "paste",
+ "recents",
+ "distinct",
+ "playing",
+ "protocols",
+ "fileonly",
+ "titleonly",
+ "timeonly",
+ "keywords",
+}
+local available_sorts = { "added-asc", "added-desc", "time-asc", "time-desc", "alphanum-asc", "alphanum-desc" }
+local search_string = ""
+local search_active = false
+
+local resume_selected = false
+local list_contents = {}
+local list_start = 0
+local list_cursor = 1
+local list_highlight_cursor = {}
+local list_drawn = false
+local list_pages = {}
+local filePath, fileTitle, fileLength
+local seekTime = 0
+local filterName = "all"
+local sortName
+
+function starts_protocol(tab, val)
+ for index, value in ipairs(tab) do
+ if val:find(value) == 1 then
+ return true
+ end
+ end
+ return false
+end
+
+function contain_value(tab, val)
+ if not tab then
+ return msg.error("check value passed")
+ end
+ if not val then
+ return msg.error("check value passed")
+ end
+
+ for index, value in ipairs(tab) do
+ if value.match(string.lower(val), string.lower(value)) then
+ return true
+ end
+ end
+
+ return false
+end
+
+function has_value(tab, val, array2d)
+ if not tab then
+ return msg.error("check value passed")
+ end
+ if not val then
+ return msg.error("check value passed")
+ end
+ if not array2d then
+ for index, value in ipairs(tab) do
+ if string.lower(value) == string.lower(val) then
+ return true
+ end
+ end
+ end
+ if array2d then
+ for i = 1, #tab do
+ if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then
+ return true
+ end
+ end
+ end
+
+ return false
+end
+
+function file_exists(name)
+ local f = io.open(name, "r")
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+function format_time(seconds, sep, decimals, style)
+ local function divmod(a, b)
+ return math.floor(a / b), a % b
+ end
+ decimals = decimals == nil and 3 or decimals
+
+ local s = seconds
+ local h, s = divmod(s, 60 * 60)
+ local m, s = divmod(s, 60)
+
+ if decimals == "truncate" then
+ s = math.floor(s)
+ decimals = 0
+ if style == "timestamp" then
+ seconds = math.floor(seconds)
+ end
+ end
+
+ if not style or style == "" or style == "default" then
+ local second_format = string.format("%%0%d.%df", 2 + (decimals > 0 and decimals + 1 or 0), decimals)
+ sep = sep and sep or ":"
+ return string.format("%02d" .. sep .. "%02d" .. sep .. second_format, h, m, s)
+ elseif style == "hms" or style == "hms-full" then
+ sep = sep ~= nil and sep or " "
+ if style == "hms-full" or h > 0 then
+ return string.format("%dh" .. sep .. "%dm" .. sep .. "%." .. tostring(decimals) .. "fs", h, m, s)
+ elseif m > 0 then
+ return string.format("%dm" .. sep .. "%." .. tostring(decimals) .. "fs", m, s)
+ else
+ return string.format("%." .. tostring(decimals) .. "fs", s)
+ end
+ elseif style == "timestamp" then
+ return string.format("%." .. tostring(decimals) .. "f", seconds)
+ elseif style == "timestamp-concise" then
+ return seconds
+ end
+end
+
+function get_file()
+ local path = mp.get_property("path")
+ if not path then
+ return
+ end
+
+ local length = (mp.get_property_number("duration") or 0)
+
+ local title = mp.get_property("media-title"):gsub('"', "")
+
+ if starts_protocol(o.logging_protocols, path) and o.prefer_filename_over_title == "protocols" then
+ title = mp.get_property("filename"):gsub('"', "")
+ elseif not starts_protocol(o.logging_protocols, path) and o.prefer_filename_over_title == "local" then
+ title = mp.get_property("filename"):gsub('"', "")
+ elseif o.prefer_filename_over_title == "all" then
+ title = mp.get_property("filename"):gsub('"', "")
+ end
+
+ return path, title, length
+end
+
+function bind_keys(keys, name, func, opts)
+ if not keys then
+ mp.add_forced_key_binding(keys, name, func, opts)
+ return
+ end
+
+ for i = 1, #keys do
+ if i == 1 then
+ mp.add_forced_key_binding(keys[i], name, func, opts)
+ else
+ mp.add_forced_key_binding(keys[i], name .. i, func, opts)
+ end
+ end
+end
+
+function unbind_keys(keys, name)
+ if not keys then
+ mp.remove_key_binding(name)
+ return
+ end
+
+ for i = 1, #keys do
+ if i == 1 then
+ mp.remove_key_binding(name)
+ else
+ mp.remove_key_binding(name .. i)
+ end
+ end
+end
+
+function esc_string(str)
+ return str:gsub("([%p])", "%%%1")
+end
+
+---------Start of LogManager---------
+--LogManager (Read and Format the List from Log)--
+function read_log(func)
+ local f = io.open(log_fullpath, "r")
+ if not f then
+ return
+ end
+ local contents = {}
+ for line in f:lines() do
+ table.insert(contents, (func(line)))
+ end
+ f:close()
+ return contents
+end
+
+function read_log_table()
+ local line_pos = 0
+ return read_log(function(line)
+ local tt, p, t, s, d, n, e, l, dt, ln, r, cp, pt
+ if line:match('^.-"(.-)"') then
+ tt = line:match('^.-"(.-)"')
+ n, p = line:match('^.-"(.-)" | (.*) | ' .. esc_string(log_length_text) .. "(.*)")
+ else
+ p = line:match("[(.*)%]]%s(.*) | " .. esc_string(log_length_text) .. "(.*)")
+ d, n, e = p:match("^(.-)([^\\/]-)%.([^\\/%.]-)%.?$")
+ end
+ dt = line:match("%[(.-)%]")
+ t = line:match(" | " .. esc_string(log_time_text) .. "(%d*%.?%d*)(.*)$")
+ ln = line:match(" | " .. esc_string(log_length_text) .. "(%d*%.?%d*)(.*)$")
+ if tonumber(ln) and tonumber(t) then
+ r = tonumber(ln) - tonumber(t)
+ else
+ r = 0
+ end
+ cp = line:match(" | .* | " .. esc_string(log_clipboard_text) .. "(copy)$")
+ pt = line:match(" | .* | " .. esc_string(log_clipboard_text) .. "(paste)$")
+ l = line
+ line_pos = line_pos + 1
+ return {
+ found_path = p,
+ found_time = t,
+ found_name = n,
+ found_title = tt,
+ found_line = l,
+ found_sequence = line_pos,
+ found_directory = d,
+ found_datetime = dt,
+ found_length = ln,
+ found_remaining = r,
+ found_copy = cp,
+ found_paste = pt,
+ }
+ end)
+end
+
+function list_sort(tab, sort)
+ if sort == "added-asc" then
+ table.sort(tab, function(a, b)
+ return a["found_sequence"] < b["found_sequence"]
+ end)
+ elseif sort == "added-desc" then
+ table.sort(tab, function(a, b)
+ return a["found_sequence"] > b["found_sequence"]
+ end)
+ elseif sort == "time-asc" then
+ table.sort(tab, function(a, b)
+ return tonumber(a["found_time"]) > tonumber(b["found_time"])
+ end)
+ elseif sort == "time-desc" then
+ table.sort(tab, function(a, b)
+ return tonumber(a["found_time"]) < tonumber(b["found_time"])
+ end)
+ elseif sort == "alphanum-asc" or sort == "alphanum-desc" then
+ local function padnum(d)
+ local dec, n = string.match(d, "(%.?)0*(.+)")
+ return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
+ end
+ if sort == "alphanum-asc" then
+ table.sort(tab, function(a, b)
+ return tostring(a["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#b)
+ > tostring(b["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#a)
+ end)
+ elseif sort == "alphanum-desc" then
+ table.sort(tab, function(a, b)
+ return tostring(a["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#b)
+ < tostring(b["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#a)
+ end)
+ end
+ end
+
+ return tab
+end
+
+function parse_header(string)
+ local osd_header_color = string.format("{\\1c&H%s}", o.header_color)
+ local osd_search_color = osd_header_color
+ if search_active == "typing" then
+ osd_search_color = string.format("{\\1c&H%s}", o.search_color_typing)
+ elseif search_active == "not_typing" then
+ osd_search_color = string.format("{\\1c&H%s}", o.search_color_not_typing)
+ end
+ local osd_msg_end = "{\\1c&HFFFFFF}"
+
+ string = string:gsub("%%total%%", #list_contents):gsub("%%cursor%%", list_cursor)
+
+ if filterName ~= "all" then
+ string = string
+ :gsub("%%filter%%", filterName)
+ :gsub("%%prefilter%%", o.header_filter_pre_text)
+ :gsub("%%afterfilter%%", o.header_filter_after_text)
+ else
+ string = string:gsub("%%filter%%", ""):gsub("%%prefilter%%", ""):gsub("%%afterfilter%%", "")
+ end
+
+ local list_total_duration = 0
+ if string:match("%listduration%%") then
+ list_total_duration = get_total_duration("found_time")
+ if list_total_duration > 0 then
+ string = string:gsub(
+ "%%listduration%%",
+ format_time(
+ list_total_duration,
+ o.header_duration_time_format[3],
+ o.header_duration_time_format[2],
+ o.header_duration_time_format[1]
+ )
+ )
+ else
+ string = string:gsub("%%listduration%%", "")
+ end
+ end
+ if list_total_duration > 0 then
+ string = string
+ :gsub("%%prelistduration%%", o.header_list_duration_pre_text)
+ :gsub("%%afterlistduration%%", o.header_list_duration_after_text)
+ else
+ string = string:gsub("%%prelistduration%%", ""):gsub("%%afterlistduration%%", "")
+ end
+
+ local list_total_length = 0
+ if string:match("%listlength%%") then
+ list_total_length = get_total_duration("found_length")
+ if list_total_length > 0 then
+ string = string:gsub(
+ "%%listlength%%",
+ format_time(
+ list_total_length,
+ o.header_length_time_format[3],
+ o.header_length_time_format[2],
+ o.header_length_time_format[1]
+ )
+ )
+ else
+ string = string:gsub("%%listlength%%", "")
+ end
+ end
+ if list_total_length > 0 then
+ string = string
+ :gsub("%%prelistlength%%", o.header_list_length_pre_text)
+ :gsub("%%afterlistlength%%", o.header_list_length_after_text)
+ else
+ string = string:gsub("%%prelistlength%%", ""):gsub("%%afterlistlength%%", "")
+ end
+
+ local list_total_remaining = 0
+ if string:match("%listremaining%%") then
+ list_total_remaining = get_total_duration("found_remaining")
+ if list_total_remaining > 0 then
+ string = string:gsub(
+ "%%listremaining%%",
+ format_time(
+ list_total_remaining,
+ o.header_remaining_time_format[3],
+ o.header_remaining_time_format[2],
+ o.header_remaining_time_format[1]
+ )
+ )
+ else
+ string = string:gsub("%%listremaining%%", "")
+ end
+ end
+ if list_total_remaining > 0 then
+ string = string
+ :gsub("%%prelistremaining%%", o.header_list_remaining_pre_text)
+ :gsub("%%afterlistremaining%%", o.header_list_remaining_after_text)
+ else
+ string = string:gsub("%%prelistremaining%%", ""):gsub("%%afterlistremaining%%", "")
+ end
+
+ if #list_highlight_cursor > 0 then
+ string = string
+ :gsub("%%highlight%%", #list_highlight_cursor)
+ :gsub("%%prehighlight%%", o.header_highlight_pre_text)
+ :gsub("%%afterhighlight%%", o.header_highlight_after_text)
+ else
+ string = string:gsub("%%highlight%%", ""):gsub("%%prehighlight%%", ""):gsub("%%afterhighlight%%", "")
+ end
+
+ if sortName and sortName ~= o.header_sort_hide_text then
+ string = string
+ :gsub("%%sort%%", sortName)
+ :gsub("%%presort%%", o.header_sort_pre_text)
+ :gsub("%%aftersort%%", o.header_sort_after_text)
+ else
+ string = string:gsub("%%sort%%", ""):gsub("%%presort%%", ""):gsub("%%aftersort%%", "")
+ end
+
+ if search_active then
+ local search_string_osd = search_string
+ if search_string_osd ~= "" then
+ search_string_osd = search_string:gsub("%%", "%%%%%%%%"):gsub("\\", "\\​"):gsub("{", "\\{")
+ end
+
+ string = string
+ :gsub("%%search%%", osd_search_color .. search_string_osd .. osd_header_color)
+ :gsub("%%presearch%%", o.header_search_pre_text)
+ :gsub("%%aftersearch%%", o.header_search_after_text)
+ else
+ string = string:gsub("%%search%%", ""):gsub("%%presearch%%", ""):gsub("%%aftersearch%%", "")
+ end
+ string = string:gsub("%%%%", "%%")
+ return string
+end
+
+function get_list_contents(filter, sort)
+ if not filter then
+ filter = filterName
+ end
+ if not sort then
+ sort = get_list_sort(filter)
+ end
+
+ local current_sort
+
+ local filtered_table = {}
+
+ local prev_list_contents
+ if list_contents ~= nil and list_contents[1] then
+ prev_list_contents = list_contents
+ else
+ prev_list_contents = read_log_table()
+ end
+
+ list_contents = read_log_table()
+ if not list_contents and not search_active or not list_contents[1] and not search_active then
+ return
+ end
+ current_sort = "added-asc"
+
+ if filter == "copy" then
+ for i = 1, #list_contents do
+ if list_contents[i].found_copy then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ if not sort then
+ active_sort = o.sort_copy_filter
+ end
+ if active_sort ~= "none" or active_sort ~= "" then
+ list_sort(filtered_table, active_sort)
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "paste" then
+ for i = 1, #list_contents do
+ if list_contents[i].found_paste then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ if not sort then
+ active_sort = o.sort_paste_filter
+ end
+ if active_sort ~= "none" or active_sort ~= "" then
+ list_sort(filtered_table, active_sort)
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "recents" then
+ table.sort(list_contents, function(a, b)
+ return a["found_sequence"] < b["found_sequence"]
+ end)
+ local unique_values = {}
+ local list_total = #list_contents
+
+ if
+ filePath == list_contents[#list_contents].found_path
+ and tonumber(list_contents[#list_contents].found_time) == 0
+ then
+ list_total = list_total - 1
+ end
+
+ for i = list_total, 1, -1 do
+ if not has_value(unique_values, list_contents[i].found_path) then
+ table.insert(unique_values, list_contents[i].found_path)
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+ table.sort(filtered_table, function(a, b)
+ return a["found_sequence"] < b["found_sequence"]
+ end)
+
+ list_contents = filtered_table
+ end
+
+ if filter == "distinct" then
+ table.sort(list_contents, function(a, b)
+ return a["found_sequence"] < b["found_sequence"]
+ end)
+ local unique_values = {}
+ local list_total = #list_contents
+
+ if
+ filePath == list_contents[#list_contents].found_path
+ and tonumber(list_contents[#list_contents].found_time) == 0
+ then
+ list_total = list_total - 1
+ end
+
+ for i = list_total, 1, -1 do
+ if
+ list_contents[i].found_directory
+ and not has_value(unique_values, list_contents[i].found_directory)
+ and not starts_protocol(protocols, list_contents[i].found_path)
+ then
+ table.insert(unique_values, list_contents[i].found_directory)
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+ table.sort(filtered_table, function(a, b)
+ return a["found_sequence"] < b["found_sequence"]
+ end)
+
+ list_contents = filtered_table
+ end
+
+ if filter == "fileonly" then
+ for i = 1, #list_contents do
+ if tonumber(list_contents[i].found_time) == 0 then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "timeonly" then
+ for i = 1, #list_contents do
+ if tonumber(list_contents[i].found_time) > 0 then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "titleonly" then
+ for i = 1, #list_contents do
+ if list_contents[i].found_title then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "protocols" then
+ for i = 1, #list_contents do
+ if starts_protocol(o.logging_protocols, list_contents[i].found_path) then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "keywords" then
+ for i = 1, #list_contents do
+ if contain_value(o.keywords_filter_list, list_contents[i].found_line) then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if filter == "playing" then
+ for i = 1, #list_contents do
+ if list_contents[i].found_path == filePath then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if search_active and search_string ~= "" then
+ local filtered_table = {}
+
+ local search_query = ""
+ for search in search_string:gmatch("[^%s]+") do
+ search_query = search_query .. ".-" .. esc_string(search)
+ end
+
+ local contents_string = ""
+ for i = 1, #list_contents do
+ if o.search_behavior == "specific" then
+ if string.lower(list_contents[i].found_path):match(string.lower(search_query)) then
+ table.insert(filtered_table, list_contents[i])
+ elseif
+ list_contents[i].found_title
+ and string.lower(list_contents[i].found_title):match(string.lower(search_query))
+ then
+ table.insert(filtered_table, list_contents[i])
+ elseif
+ tonumber(list_contents[i].found_time) > 0
+ and format_time(
+ list_contents[i].found_time,
+ o.osd_time_format[3],
+ o.osd_time_format[2],
+ o.osd_time_format[1]
+ ):match(search_query)
+ then
+ table.insert(filtered_table, list_contents[i])
+ elseif string.lower(list_contents[i].found_datetime):match(string.lower(search_query)) then
+ table.insert(filtered_table, list_contents[i])
+ end
+ elseif o.search_behavior == "any" then
+ contents_string = list_contents[i].found_datetime
+ .. (list_contents[i].found_title or "")
+ .. list_contents[i].found_path
+ if tonumber(list_contents[i].found_time) > 0 then
+ contents_string = contents_string
+ .. format_time(
+ list_contents[i].found_time,
+ o.osd_time_format[3],
+ o.osd_time_format[2],
+ o.osd_time_format[1]
+ )
+ end
+ elseif o.search_behavior == "any-notime" then
+ contents_string = list_contents[i].found_datetime
+ .. (list_contents[i].found_title or "")
+ .. list_contents[i].found_path
+ end
+
+ if string.lower(contents_string):match(string.lower(search_query)) then
+ table.insert(filtered_table, list_contents[i])
+ end
+ end
+
+ list_contents = filtered_table
+ end
+
+ if sort ~= current_sort then
+ list_sort(list_contents, sort)
+ end
+
+ if not list_contents and not search_active or not list_contents[1] and not search_active then
+ return
+ end
+end
+
+function get_list_sort(filter)
+ if not filter then
+ filter = filterName
+ end
+
+ local sort
+ for i = 1, #o.list_filters_sort do
+ if o.list_filters_sort[i][1] == filter then
+ if has_value(available_sorts, o.list_filters_sort[i][2]) then
+ sort = o.list_filters_sort[i][2]
+ end
+ break
+ end
+ end
+
+ if not sort and has_value(available_sorts, o.list_default_sort) then
+ sort = o.list_default_sort
+ end
+
+ if not sort then
+ sort = "added-asc"
+ end
+
+ return sort
+end
+
+function draw_list()
+ local osd_msg = ""
+ local osd_index = ""
+ local osd_key = ""
+ local osd_color = ""
+ local key = 0
+ local osd_text = string.format(
+ "{\\an%f{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}",
+ o.list_alignment,
+ o.text_scale,
+ o.text_scale,
+ o.text_border,
+ o.text_color
+ )
+ local osd_cursor = string.format(
+ "{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}",
+ o.list_alignment,
+ o.text_cursor_scale,
+ o.text_cursor_scale,
+ o.text_cursor_border,
+ o.text_cursor_color
+ )
+ local osd_header = string.format(
+ "{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}",
+ o.list_alignment,
+ o.header_scale,
+ o.header_scale,
+ o.header_border,
+ o.header_color
+ )
+ local osd_msg_end = "{\\1c&HFFFFFF}"
+ local osd_time_type = "found_time"
+
+ if o.text_time_type == "length" then
+ osd_time_type = "found_length"
+ elseif o.text_time_type == "remaining" then
+ osd_time_type = "found_remaining"
+ end
+
+ if o.header_text ~= "" then
+ osd_msg = osd_msg .. osd_header .. parse_header(o.header_text)
+ osd_msg = osd_msg .. "\\h\\N\\N" .. osd_msg_end
+ end
+
+ if search_active and not list_contents[1] then
+ osd_msg = osd_msg .. "No search results found" .. osd_msg_end
+ end
+
+ if o.list_middle_loader then
+ list_start = list_cursor - math.floor(o.list_show_amount / 2)
+ else
+ list_start = list_cursor - o.list_show_amount
+ end
+
+ local showall = false
+ local showrest = false
+ if list_start < 0 then
+ list_start = 0
+ end
+ if #list_contents <= o.list_show_amount then
+ list_start = 0
+ showall = true
+ end
+ if list_start > math.max(#list_contents - o.list_show_amount - 1, 0) then
+ list_start = #list_contents - o.list_show_amount
+ showrest = true
+ end
+ if list_start > 0 and not showall then
+ osd_msg = osd_msg .. o.list_sliced_prefix .. osd_msg_end
+ end
+ for i = list_start, list_start + o.list_show_amount - 1, 1 do
+ if i == #list_contents then
+ break
+ end
+
+ if o.show_paths then
+ p = list_contents[#list_contents - i].found_path or list_contents[#list_contents - i].found_name or ""
+ else
+ p = list_contents[#list_contents - i].found_name or list_contents[#list_contents - i].found_path or ""
+ end
+
+ if o.slice_longfilenames and p:len() > o.slice_longfilenames_amount then
+ p = p:sub(1, o.slice_longfilenames_amount) .. "..."
+ end
+
+ if o.quickselect_0to9_keybind and o.list_show_amount <= 10 and o.quickselect_0to9_pre_text then
+ key = 1 + key
+ if key == 10 then
+ key = 0
+ end
+ osd_key = "(" .. key .. ") "
+ end
+
+ if o.show_item_number then
+ osd_index = (i + 1) .. ". "
+ end
+
+ if i + 1 == list_cursor then
+ osd_color = osd_cursor
+ else
+ osd_color = osd_text
+ end
+
+ for j = 1, #list_highlight_cursor do
+ if list_highlight_cursor[j] and list_highlight_cursor[j][1] == i + 1 then
+ osd_msg = osd_msg .. osd_color .. esc_string(o.text_highlight_pre_text)
+ end
+ end
+
+ osd_msg = osd_msg .. osd_color .. osd_key .. osd_index .. p
+
+ if
+ list_contents[#list_contents - i][osd_time_type]
+ and tonumber(list_contents[#list_contents - i][osd_time_type]) > 0
+ then
+ osd_msg = osd_msg
+ .. o.time_seperator
+ .. format_time(
+ list_contents[#list_contents - i][osd_time_type],
+ o.list_time_format[3],
+ o.list_time_format[2],
+ o.list_time_format[1]
+ )
+ end
+
+ if list_contents[#list_contents - i].found_copy then
+ osd_msg = osd_msg .. o.copy_seperator
+ end
+
+ if list_contents[#list_contents - i].found_paste then
+ osd_msg = osd_msg .. o.paste_seperator
+ end
+
+ osd_msg = osd_msg .. "\\h\\N\\N" .. osd_msg_end
+
+ if i == list_start + o.list_show_amount - 1 and not showall and not showrest then
+ osd_msg = osd_msg .. o.list_sliced_suffix
+ end
+ end
+ mp.set_osd_ass(0, 0, osd_msg)
+end
+
+function list_empty_error_msg()
+ if list_contents ~= nil and list_contents[1] then
+ return
+ end
+ local msg_text
+ if filterName ~= "all" then
+ msg_text = filterName .. " filter in Clipboard Empty"
+ else
+ msg_text = "Clipboard Empty"
+ end
+ msg.info(msg_text)
+ if o.osd_messages == true and not list_drawn then
+ mp.osd_message(msg_text)
+ end
+end
+
+function display_list(filter, sort, action)
+ if not filter or not has_value(available_filters, filter) then
+ filter = "all"
+ end
+ if not sortName then
+ sortName = get_list_sort(filter)
+ end
+
+ local prev_sort = sortName
+ if not has_value(available_sorts, prev_sort) then
+ prev_sort = get_list_sort()
+ end
+
+ if not sort then
+ sort = get_list_sort(filter)
+ end
+ sortName = sort
+
+ local prev_filter = filterName
+ filterName = filter
+
+ get_list_contents(filter, sort)
+
+ if action ~= "hide-osd" then
+ if not list_contents or not list_contents[1] then
+ list_empty_error_msg()
+ filterName = prev_filter
+ get_list_contents(filterName)
+ return
+ end
+ end
+ if not list_contents and not search_active or not list_contents[1] and not search_active then
+ return
+ end
+
+ if not has_value(o.filters_and_sequence, filter) then
+ table.insert(o.filters_and_sequence, filter)
+ end
+
+ local insert_new = false
+
+ local trigger_close_list = false
+ local trigger_initial_list = false
+
+ if not list_pages or not list_pages[1] then
+ table.insert(list_pages, { filter, 1, 1, {}, sort })
+ else
+ for i = 1, #list_pages do
+ if list_pages[i][1] == filter then
+ list_pages[i][3] = list_pages[i][3] + 1
+ insert_new = false
+ break
+ else
+ insert_new = true
+ end
+ end
+ end
+
+ if insert_new then
+ table.insert(list_pages, { filter, 1, 1, {}, sort })
+ end
+
+ for i = 1, #list_pages do
+ if not search_active and list_pages[i][1] == prev_filter then
+ list_pages[i][2] = list_cursor
+ list_pages[i][4] = list_highlight_cursor
+ list_pages[i][5] = prev_sort
+ end
+ if list_pages[i][1] ~= filter then
+ list_pages[i][3] = 0
+ end
+ if list_pages[i][3] == 2 and filter == "all" and o.main_list_keybind_twice_exits then
+ trigger_close_list = true
+ elseif list_pages[i][3] == 2 and list_pages[1][1] == filter then
+ trigger_close_list = true
+ elseif list_pages[i][3] == 2 then
+ trigger_initial_list = true
+ end
+ end
+
+ if trigger_initial_list then
+ display_list(list_pages[1][1], nil, "hide-osd")
+ return
+ end
+
+ if trigger_close_list then
+ list_close_and_trash_collection()
+ return
+ end
+
+ if not search_active then
+ get_page_properties(filter)
+ else
+ update_search_results("", "")
+ end
+ draw_list()
+ if utils.shared_script_property_set then
+ utils.shared_script_property_set("smartcopypaste-menu-open", "yes")
+ end
+ mp.set_property("user-data/smartcopypaste/menu-open", "yes")
+ if o.toggle_idlescreen then
+ mp.commandv("script-message", "osc-idlescreen", "no", "no_osd")
+ end
+ list_drawn = true
+ if not search_active then
+ get_list_keybinds()
+ end
+end
+
+--End of LogManager (Read and Format the List from Log)--
+
+--LogManager Navigation--
+function select(pos, action)
+ if not search_active then
+ if not list_contents or not list_contents[1] then
+ list_close_and_trash_collection()
+ return
+ end
+ end
+
+ local list_cursor_temp = list_cursor + pos
+ if list_cursor_temp > 0 and list_cursor_temp <= #list_contents then
+ list_cursor = list_cursor_temp
+
+ if action == "highlight" then
+ if not has_value(list_highlight_cursor, list_cursor, 1) then
+ if pos > -1 then
+ for i = pos, 1, -1 do
+ if not has_value(list_highlight_cursor, list_cursor - i, 1) then
+ table.insert(
+ list_highlight_cursor,
+ { list_cursor - i, list_contents[#list_contents + 1 + i - list_cursor] }
+ )
+ end
+ end
+ else
+ for i = pos, -1, 1 do
+ if not has_value(list_highlight_cursor, list_cursor - i, 1) then
+ table.insert(
+ list_highlight_cursor,
+ { list_cursor - i, list_contents[#list_contents + 1 + i - list_cursor] }
+ )
+ end
+ end
+ end
+ table.insert(list_highlight_cursor, { list_cursor, list_contents[#list_contents + 1 - list_cursor] })
+ else
+ for i = 1, #list_highlight_cursor do
+ if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor then
+ table.remove(list_highlight_cursor, i)
+ end
+ end
+ if pos > -1 then
+ for i = 1, #list_highlight_cursor do
+ for j = pos, 1, -1 do
+ if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor - j then
+ table.remove(list_highlight_cursor, i)
+ end
+ end
+ end
+ else
+ for i = #list_highlight_cursor, 1, -1 do
+ for j = pos, -1, 1 do
+ if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor - j then
+ table.remove(list_highlight_cursor, i)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ if o.loop_through_list then
+ if list_cursor_temp > #list_contents then
+ list_cursor = 1
+ elseif list_cursor_temp < 1 then
+ list_cursor = #list_contents
+ end
+ end
+
+ draw_list()
+end
+
+function list_move_up(action)
+ select(-1, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_move_down(action)
+ select(1, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_move_first(action)
+ select(1 - list_cursor, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_move_last(action)
+ select(#list_contents - list_cursor, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_page_up(action)
+ select(list_start + 1 - list_cursor, action)
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_page_down(action)
+ if o.list_middle_loader then
+ if #list_contents < o.list_show_amount then
+ select(#list_contents - list_cursor, action)
+ else
+ select(o.list_show_amount + list_start - list_cursor, action)
+ end
+ else
+ if o.list_show_amount > list_cursor then
+ select(o.list_show_amount - list_cursor, action)
+ elseif #list_contents - list_cursor >= o.list_show_amount then
+ select(o.list_show_amount, action)
+ else
+ select(#list_contents - list_cursor, action)
+ end
+ end
+
+ if search_active and o.search_not_typing_smartly then
+ list_search_not_typing_mode(true)
+ end
+end
+
+function list_highlight_all()
+ get_list_contents(filterName)
+ if not list_contents or not list_contents[1] then
+ return
+ end
+
+ if #list_highlight_cursor < #list_contents then
+ for i = 1, #list_contents do
+ if not has_value(list_highlight_cursor, i, 1) then
+ table.insert(list_highlight_cursor, { i, list_contents[#list_contents + 1 - i] })
+ end
+ end
+ select(0)
+ else
+ list_unhighlight_all()
+ end
+end
+
+function list_unhighlight_all()
+ if not list_highlight_cursor or not list_highlight_cursor[1] then
+ return
+ end
+ list_highlight_cursor = {}
+ select(0)
+end
+--End of LogManager Navigation--
+
+--LogManager Actions--
+function load(list_cursor, add_playlist, target_time)
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ if not target_time then
+ seekTime = tonumber(list_contents[#list_contents - list_cursor + 1].found_time) + o.resume_offset
+ if seekTime < 0 then
+ seekTime = 0
+ end
+ else
+ seekTime = target_time
+ end
+ if
+ file_exists(list_contents[#list_contents - list_cursor + 1].found_path)
+ or starts_protocol(protocols, list_contents[#list_contents - list_cursor + 1].found_path)
+ then
+ if not add_playlist then
+ if filePath ~= list_contents[#list_contents - list_cursor + 1].found_path then
+ mp.commandv("loadfile", list_contents[#list_contents - list_cursor + 1].found_path)
+ resume_selected = true
+ else
+ mp.commandv("seek", seekTime, "absolute", "exact")
+ list_close_and_trash_collection()
+ end
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Loaded:\n"
+ .. list_contents[#list_contents - list_cursor + 1].found_name
+ .. o.time_seperator
+ .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ msg.info(
+ "Loaded the below file:\n"
+ .. list_contents[#list_contents - list_cursor + 1].found_name
+ .. " | "
+ .. format_time(seekTime)
+ )
+ else
+ mp.commandv("loadfile", list_contents[#list_contents - list_cursor + 1].found_path, "append-play")
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Added into Playlist:\n" .. list_contents[#list_contents - list_cursor + 1].found_name .. " "
+ )
+ end
+ msg.info(
+ "Added the below file into playlist:\n" .. list_contents[#list_contents - list_cursor + 1].found_path
+ )
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message("File Doesn't Exist:\n" .. list_contents[#list_contents - list_cursor + 1].found_path)
+ end
+ msg.info(
+ "The file below doesn't seem to exist:\n" .. list_contents[#list_contents - list_cursor + 1].found_path
+ )
+ return
+ end
+end
+
+function list_select()
+ load(list_cursor)
+end
+
+function list_add_playlist(action)
+ if not action then
+ load(list_cursor, true)
+ elseif action == "highlight" then
+ if not list_highlight_cursor or not list_highlight_cursor[1] then
+ return
+ end
+ local file_ignored_total = 0
+
+ for i = 1, #list_highlight_cursor do
+ if
+ file_exists(list_highlight_cursor[i][2].found_path)
+ or starts_protocol(protocols, list_highlight_cursor[i][2].found_path)
+ then
+ mp.commandv("loadfile", list_highlight_cursor[i][2].found_path, "append-play")
+ else
+ msg.warn(
+ "The below file was not added into playlist as it does not seem to exist:\n"
+ .. list_highlight_cursor[i][2].found_path
+ )
+ file_ignored_total = file_ignored_total + 1
+ end
+ end
+ if o.osd_messages == true then
+ if file_ignored_total > 0 then
+ mp.osd_message(
+ "Added into Playlist "
+ .. #list_highlight_cursor - file_ignored_total
+ .. " Item/s\nIgnored "
+ .. file_ignored_total
+ .. " Item/s That Do Not Exist"
+ )
+ else
+ mp.osd_message("Added into Playlist " .. #list_highlight_cursor - file_ignored_total .. " Item/s")
+ end
+ end
+ if file_ignored_total > 0 then
+ msg.warn("Ignored a total of " .. file_ignored_total .. " Item/s that does not seem to exist")
+ end
+ msg.info("Added into playlist a total of " .. #list_highlight_cursor - file_ignored_total .. " item/s")
+ end
+end
+
+function delete_log_entry_specific(target_index, target_path, target_time)
+ local trigger_delete = false
+ list_contents = read_log_table()
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ if target_index == "last" then
+ target_index = #list_contents
+ end
+ if not target_index then
+ return
+ end
+
+ if target_index and target_path and target_time then
+ if
+ list_contents[target_index].found_path == target_path
+ and tonumber(list_contents[target_index].found_time) == target_time
+ then
+ table.remove(list_contents, target_index)
+ trigger_delete = true
+ end
+ elseif target_index and target_path and not target_time then
+ if list_contents[target_index].found_path == target_path then
+ table.remove(list_contents, target_index)
+ trigger_delete = true
+ end
+ elseif target_index and target_time and not target_path then
+ if tonumber(list_contents[target_index].found_time) == target_time then
+ table.remove(list_contents, target_index)
+ trigger_delete = true
+ end
+ elseif target_index and not target_path and not target_time then
+ table.remove(list_contents, target_index)
+ trigger_delete = true
+ end
+
+ if not trigger_delete then
+ return
+ end
+ local f = io.open(log_fullpath, "w+")
+ if list_contents ~= nil and list_contents[1] then
+ for i = 1, #list_contents do
+ f:write(("%s\n"):format(list_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+
+function delete_log_entry(multiple, round, target_path, target_time, entry_limit)
+ if not target_path then
+ target_path = filePath
+ end
+ if not target_time then
+ target_time = seekTime
+ end
+ list_contents = read_log_table()
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ local trigger_delete = false
+
+ if not multiple then
+ for i = #list_contents, 1, -1 do
+ if not round then
+ if
+ list_contents[i].found_path == target_path
+ and tonumber(list_contents[i].found_time) == target_time
+ then
+ table.remove(list_contents, i)
+ trigger_delete = true
+ break
+ end
+ else
+ if
+ list_contents[i].found_path == target_path
+ and math.floor(tonumber(list_contents[i].found_time)) == target_time
+ then
+ table.remove(list_contents, i)
+ trigger_delete = true
+ break
+ end
+ end
+ end
+ else
+ for i = #list_contents, 1, -1 do
+ if not round then
+ if
+ list_contents[i].found_path == target_path
+ and tonumber(list_contents[i].found_time) == target_time
+ then
+ table.remove(list_contents, i)
+ trigger_delete = true
+ end
+ else
+ if
+ list_contents[i].found_path == target_path
+ and math.floor(tonumber(list_contents[i].found_time)) == target_time
+ then
+ table.remove(list_contents, i)
+ trigger_delete = true
+ end
+ end
+ end
+ end
+
+ if entry_limit and entry_limit > -1 then
+ local entries_found = 0
+ for i = #list_contents, 1, -1 do
+ if list_contents[i].found_path == target_path and entries_found < entry_limit then
+ entries_found = entries_found + 1
+ elseif list_contents[i].found_path == target_path and entries_found >= entry_limit then
+ table.remove(list_contents, i)
+ trigger_delete = true
+ end
+ end
+ end
+
+ if not trigger_delete then
+ return
+ end
+ local f = io.open(log_fullpath, "w+")
+ if list_contents ~= nil and list_contents[1] then
+ for i = 1, #list_contents do
+ f:write(("%s\n"):format(list_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+
+function delete_log_entry_highlighted()
+ if not list_highlight_cursor or not list_highlight_cursor[1] then
+ return
+ end
+ list_contents = read_log_table()
+ if not list_contents or not list_contents[1] then
+ return
+ end
+
+ local list_contents_length = #list_contents
+
+ for i = 1, list_contents_length do
+ for j = 1, #list_highlight_cursor do
+ if list_contents[list_contents_length + 1 - i] then
+ if
+ list_contents[list_contents_length + 1 - i].found_sequence
+ == list_highlight_cursor[j][2].found_sequence
+ then
+ table.remove(list_contents, list_contents_length + 1 - i)
+ end
+ end
+ end
+ end
+
+ msg.info("Deleted " .. #list_highlight_cursor .. " Item/s")
+
+ list_unhighlight_all()
+
+ local f = io.open(log_fullpath, "w+")
+ if list_contents ~= nil and list_contents[1] then
+ for i = 1, #list_contents do
+ f:write(("%s\n"):format(list_contents[i].found_line))
+ end
+ end
+ f:close()
+end
+
+function delete_selected()
+ filePath = list_contents[#list_contents - list_cursor + 1].found_path
+ fileTitle = list_contents[#list_contents - list_cursor + 1].found_name
+ seekTime = tonumber(list_contents[#list_contents - list_cursor + 1].found_time)
+ if not filePath and not seekTime then
+ msg.info("Failed to delete")
+ return
+ end
+ delete_log_entry()
+ msg.info('Deleted "' .. filePath .. '" | ' .. format_time(seekTime))
+ filePath, fileTitle, fileLength = get_file()
+end
+
+function list_delete(action)
+ if not action then
+ delete_selected()
+ elseif action == "highlight" then
+ delete_log_entry_highlighted()
+ end
+ get_list_contents()
+ if not list_contents or not list_contents[1] then
+ list_close_and_trash_collection()
+ return
+ end
+ if list_cursor < #list_contents + 1 then
+ select(0)
+ else
+ list_move_last()
+ end
+end
+
+function get_total_duration(action)
+ if not list_contents or not list_contents[1] then
+ return 0
+ end
+ local list_total_duration = 0
+ if action == "found_time" or action == "found_length" or action == "found_remaining" then
+ for i = #list_contents, 1, -1 do
+ if tonumber(list_contents[i][action]) > 0 then
+ list_total_duration = list_total_duration + list_contents[i][action]
+ end
+ end
+ end
+ return list_total_duration
+end
+
+function list_cycle_sort()
+ local next_sort
+ for i = 1, #available_sorts do
+ if sortName == available_sorts[i] then
+ if i == #available_sorts then
+ next_sort = available_sorts[1]
+ break
+ else
+ next_sort = available_sorts[i + 1]
+ break
+ end
+ end
+ end
+ if not next_sort then
+ return
+ end
+ get_list_contents(filterName, next_sort)
+ sortName = next_sort
+ update_list_highlist_cursor()
+ select(0)
+end
+
+function update_list_highlist_cursor()
+ if not list_highlight_cursor or not list_highlight_cursor[1] then
+ return
+ end
+
+ local temp_list_highlight_cursor = {}
+ for i = 1, #list_contents do
+ for j = 1, #list_highlight_cursor do
+ if list_contents[#list_contents + 1 - i].found_sequence == list_highlight_cursor[j][2].found_sequence then
+ table.insert(temp_list_highlight_cursor, { i, list_highlight_cursor[j][2] })
+ end
+ end
+ end
+
+ list_highlight_cursor = temp_list_highlight_cursor
+end
+
+--End of LogManager Actions--
+
+--LogManager Filter Functions--
+function get_page_properties(filter)
+ if not filter then
+ return
+ end
+ for i = 1, #list_pages do
+ if list_pages[i][1] == filter then
+ list_cursor = list_pages[i][2]
+ list_highlight_cursor = list_pages[i][4]
+ sortName = list_pages[i][5]
+ end
+ end
+ if list_cursor > #list_contents then
+ list_move_last()
+ end
+end
+
+function select_filter_sequence(pos)
+ if not list_drawn then
+ return
+ end
+ local curr_pos
+ local target_pos
+
+ for i = 1, #o.filters_and_sequence do
+ if filterName == o.filters_and_sequence[i] then
+ curr_pos = i
+ end
+ end
+
+ if curr_pos and pos > -1 then
+ for i = curr_pos, #o.filters_and_sequence do
+ if o.filters_and_sequence[i + pos] then
+ get_list_contents(o.filters_and_sequence[i + pos])
+ if list_contents ~= nil and list_contents[1] then
+ target_pos = i + pos
+ break
+ end
+ end
+ end
+ elseif curr_pos and pos < 0 then
+ for i = curr_pos, 0, -1 do
+ if o.filters_and_sequence[i + pos] then
+ get_list_contents(o.filters_and_sequence[i + pos])
+ if list_contents ~= nil and list_contents[1] then
+ target_pos = i + pos
+ break
+ end
+ end
+ end
+ end
+
+ if o.loop_through_filters then
+ if not target_pos and pos > -1 or target_pos and target_pos > #o.filters_and_sequence then
+ for i = 1, #o.filters_and_sequence do
+ get_list_contents(o.filters_and_sequence[i])
+ if list_contents ~= nil and list_contents[1] then
+ target_pos = i
+ break
+ end
+ end
+ end
+ if not target_pos and pos < 0 or target_pos and target_pos < 1 then
+ for i = #o.filters_and_sequence, 1, -1 do
+ get_list_contents(o.filters_and_sequence[i])
+ if list_contents ~= nil and list_contents[1] then
+ target_pos = i
+ break
+ end
+ end
+ end
+ end
+
+ if o.filters_and_sequence[target_pos] then
+ display_list(o.filters_and_sequence[target_pos], nil, "hide-osd")
+ end
+end
+
+function list_filter_next()
+ select_filter_sequence(1)
+end
+function list_filter_previous()
+ select_filter_sequence(-1)
+end
+--End of LogManager Filter Functions--
+
+--LogManager (List Bind and Unbind)--
+function get_list_keybinds()
+ bind_keys(o.list_ignored_keybind, "ignore")
+ bind_keys(o.list_move_up_keybind, "move-up", list_move_up, "repeatable")
+ bind_keys(o.list_move_down_keybind, "move-down", list_move_down, "repeatable")
+ bind_keys(o.list_move_first_keybind, "move-first", list_move_first, "repeatable")
+ bind_keys(o.list_move_last_keybind, "move-last", list_move_last, "repeatable")
+ bind_keys(o.list_page_up_keybind, "page-up", list_page_up, "repeatable")
+ bind_keys(o.list_page_down_keybind, "page-down", list_page_down, "repeatable")
+ bind_keys(o.list_select_keybind, "list-select", list_select)
+ bind_keys(o.list_add_playlist_keybind, "list-add-playlist", list_add_playlist)
+ bind_keys(o.list_add_playlist_highlighted_keybind, "list-add-playlist-highlight", function()
+ list_add_playlist("highlight")
+ end)
+ bind_keys(o.list_delete_keybind, "list-delete", list_delete)
+ bind_keys(o.list_delete_highlighted_keybind, "list-delete-highlight", function()
+ list_delete("highlight")
+ end)
+ bind_keys(o.next_filter_sequence_keybind, "list-filter-next", list_filter_next)
+ bind_keys(o.previous_filter_sequence_keybind, "list-filter-previous", list_filter_previous)
+ bind_keys(o.list_search_activate_keybind, "list-search-activate", list_search_activate)
+ bind_keys(o.list_highlight_all_keybind, "list-highlight-all", list_highlight_all)
+ bind_keys(o.list_unhighlight_all_keybind, "list-unhighlight-all", list_unhighlight_all)
+ bind_keys(o.list_cycle_sort_keybind, "list-cycle-sort", list_cycle_sort)
+
+ for i = 1, #o.list_highlight_move_keybind do
+ for j = 1, #o.list_move_up_keybind do
+ mp.add_forced_key_binding(
+ o.list_highlight_move_keybind[i] .. "+" .. o.list_move_up_keybind[j],
+ "highlight-move-up" .. j,
+ function()
+ list_move_up("highlight")
+ end,
+ "repeatable"
+ )
+ end
+ for j = 1, #o.list_move_down_keybind do
+ mp.add_forced_key_binding(
+ o.list_highlight_move_keybind[i] .. "+" .. o.list_move_down_keybind[j],
+ "highlight-move-down" .. j,
+ function()
+ list_move_down("highlight")
+ end,
+ "repeatable"
+ )
+ end
+ for j = 1, #o.list_move_first_keybind do
+ mp.add_forced_key_binding(
+ o.list_highlight_move_keybind[i] .. "+" .. o.list_move_first_keybind[j],
+ "highlight-move-first" .. j,
+ function()
+ list_move_first("highlight")
+ end,
+ "repeatable"
+ )
+ end
+ for j = 1, #o.list_move_last_keybind do
+ mp.add_forced_key_binding(
+ o.list_highlight_move_keybind[i] .. "+" .. o.list_move_last_keybind[j],
+ "highlight-move-last" .. j,
+ function()
+ list_move_last("highlight")
+ end,
+ "repeatable"
+ )
+ end
+ for j = 1, #o.list_page_up_keybind do
+ mp.add_forced_key_binding(
+ o.list_highlight_move_keybind[i] .. "+" .. o.list_page_up_keybind[j],
+ "highlight-page-up" .. j,
+ function()
+ list_page_up("highlight")
+ end,
+ "repeatable"
+ )
+ end
+ for j = 1, #o.list_page_down_keybind do
+ mp.add_forced_key_binding(
+ o.list_highlight_move_keybind[i] .. "+" .. o.list_page_down_keybind[j],
+ "highlight-page-down" .. j,
+ function()
+ list_page_down("highlight")
+ end,
+ "repeatable"
+ )
+ end
+ end
+
+ if not search_active then
+ bind_keys(o.list_close_keybind, "list-close", list_close_and_trash_collection)
+ end
+
+ for i = 1, #o.list_filter_jump_keybind do
+ mp.add_forced_key_binding(o.list_filter_jump_keybind[i][1], "list-filter-jump" .. i, function()
+ display_list(o.list_filter_jump_keybind[i][2])
+ end)
+ end
+
+ for i = 1, #o.open_list_keybind do
+ if i == 1 then
+ mp.remove_key_binding("open-list")
+ else
+ mp.remove_key_binding("open-list" .. i)
+ end
+ end
+
+ if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then
+ mp.add_forced_key_binding("1", "recent-1", function()
+ load(list_start + 1)
+ end)
+ mp.add_forced_key_binding("2", "recent-2", function()
+ load(list_start + 2)
+ end)
+ mp.add_forced_key_binding("3", "recent-3", function()
+ load(list_start + 3)
+ end)
+ mp.add_forced_key_binding("4", "recent-4", function()
+ load(list_start + 4)
+ end)
+ mp.add_forced_key_binding("5", "recent-5", function()
+ load(list_start + 5)
+ end)
+ mp.add_forced_key_binding("6", "recent-6", function()
+ load(list_start + 6)
+ end)
+ mp.add_forced_key_binding("7", "recent-7", function()
+ load(list_start + 7)
+ end)
+ mp.add_forced_key_binding("8", "recent-8", function()
+ load(list_start + 8)
+ end)
+ mp.add_forced_key_binding("9", "recent-9", function()
+ load(list_start + 9)
+ end)
+ mp.add_forced_key_binding("0", "recent-0", function()
+ load(list_start + 10)
+ end)
+ end
+end
+
+function unbind_list_keys()
+ unbind_keys(o.list_ignored_keybind, "ignore")
+ unbind_keys(o.list_move_up_keybind, "move-up")
+ unbind_keys(o.list_move_down_keybind, "move-down")
+ unbind_keys(o.list_move_first_keybind, "move-first")
+ unbind_keys(o.list_move_last_keybind, "move-last")
+ unbind_keys(o.list_page_up_keybind, "page-up")
+ unbind_keys(o.list_page_down_keybind, "page-down")
+ unbind_keys(o.list_select_keybind, "list-select")
+ unbind_keys(o.list_add_playlist_keybind, "list-add-playlist")
+ unbind_keys(o.list_add_playlist_highlighted_keybind, "list-add-playlist-highlight")
+ unbind_keys(o.list_delete_keybind, "list-delete")
+ unbind_keys(o.list_delete_highlighted_keybind, "list-delete-highlight")
+ unbind_keys(o.list_close_keybind, "list-close")
+ unbind_keys(o.next_filter_sequence_keybind, "list-filter-next")
+ unbind_keys(o.previous_filter_sequence_keybind, "list-filter-previous")
+ unbind_keys(o.list_highlight_all_keybind, "list-highlight-all")
+ unbind_keys(o.list_highlight_all_keybind, "list-unhighlight-all")
+ unbind_keys(o.list_cycle_sort_keybind, "list-cycle-sort")
+
+ for i = 1, #o.list_move_up_keybind do
+ mp.remove_key_binding("highlight-move-up" .. i)
+ end
+ for i = 1, #o.list_move_down_keybind do
+ mp.remove_key_binding("highlight-move-down" .. i)
+ end
+ for i = 1, #o.list_move_first_keybind do
+ mp.remove_key_binding("highlight-move-first" .. i)
+ end
+ for i = 1, #o.list_move_last_keybind do
+ mp.remove_key_binding("highlight-move-last" .. i)
+ end
+ for i = 1, #o.list_page_up_keybind do
+ mp.remove_key_binding("highlight-page-up" .. i)
+ end
+ for i = 1, #o.list_page_down_keybind do
+ mp.remove_key_binding("highlight-page-down" .. i)
+ end
+
+ for i = 1, #o.list_filter_jump_keybind do
+ mp.remove_key_binding("list-filter-jump" .. i)
+ end
+
+ for i = 1, #o.open_list_keybind do
+ if i == 1 then
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list", function()
+ display_list(o.open_list_keybind[i][2])
+ end)
+ else
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list" .. i, function()
+ display_list(o.open_list_keybind[i][2])
+ end)
+ end
+ end
+
+ if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then
+ mp.remove_key_binding("recent-1")
+ mp.remove_key_binding("recent-2")
+ mp.remove_key_binding("recent-3")
+ mp.remove_key_binding("recent-4")
+ mp.remove_key_binding("recent-5")
+ mp.remove_key_binding("recent-6")
+ mp.remove_key_binding("recent-7")
+ mp.remove_key_binding("recent-8")
+ mp.remove_key_binding("recent-9")
+ mp.remove_key_binding("recent-0")
+ end
+end
+
+function list_close_and_trash_collection()
+ if utils.shared_script_property_set then
+ utils.shared_script_property_set("smartcopypaste-menu-open", "no")
+ end
+ mp.set_property("user-data/smartcopypaste/menu-open", "no")
+ if o.toggle_idlescreen then
+ mp.commandv("script-message", "osc-idlescreen", "yes", "no_osd")
+ end
+ unbind_list_keys()
+ unbind_search_keys()
+ mp.set_osd_ass(0, 0, "")
+ list_drawn = false
+ list_cursor = 1
+ list_start = 0
+ filterName = "all"
+ list_pages = {}
+ search_string = ""
+ search_active = false
+ list_highlight_cursor = {}
+ sortName = nil
+end
+--End of LogManager (List Bind and Unbind)--
+
+--LogManager Search Feature--
+function list_search_exit()
+ search_active = false
+ get_list_contents(filterName)
+ get_page_properties(filterName)
+ select(0)
+ unbind_search_keys()
+ get_list_keybinds()
+end
+
+function list_search_not_typing_mode(auto_triggered)
+ if auto_triggered then
+ if search_string ~= "" and list_contents[1] then
+ search_active = "not_typing"
+ elseif not list_contents[1] then
+ return
+ else
+ search_active = false
+ end
+ else
+ if search_string ~= "" then
+ search_active = "not_typing"
+ else
+ search_active = false
+ end
+ end
+ select(0)
+ unbind_search_keys()
+ get_list_keybinds()
+end
+
+function list_search_activate()
+ if not list_drawn then
+ return
+ end
+ if search_active == "typing" then
+ list_search_exit()
+ return
+ end
+ search_active = "typing"
+
+ for i = 1, #list_pages do
+ if list_pages[i][1] == filterName then
+ list_pages[i][2] = list_cursor
+ list_pages[i][4] = list_highlight_cursor
+ list_pages[i][5] = sortName
+ end
+ end
+
+ update_search_results("", "")
+ bind_search_keys()
+end
+
+function update_search_results(character, action)
+ if not character then
+ character = ""
+ end
+ if action == "string_del" then
+ search_string = search_string:sub(1, -2)
+ end
+ search_string = search_string .. character
+ local prev_contents_length = #list_contents
+ get_list_contents(filterName)
+
+ if prev_contents_length ~= #list_contents then
+ list_highlight_cursor = {}
+ end
+
+ if character ~= "" and #list_contents > 0 or action ~= nil and #list_contents > 0 then
+ select(1 - list_cursor)
+ elseif #list_contents == 0 then
+ list_cursor = 0
+ select(list_cursor)
+ else
+ select(0)
+ end
+end
+
+function bind_search_keys()
+ mp.add_forced_key_binding("a", "search_string_a", function()
+ update_search_results("a")
+ end, "repeatable")
+ mp.add_forced_key_binding("b", "search_string_b", function()
+ update_search_results("b")
+ end, "repeatable")
+ mp.add_forced_key_binding("c", "search_string_c", function()
+ update_search_results("c")
+ end, "repeatable")
+ mp.add_forced_key_binding("d", "search_string_d", function()
+ update_search_results("d")
+ end, "repeatable")
+ mp.add_forced_key_binding("e", "search_string_e", function()
+ update_search_results("e")
+ end, "repeatable")
+ mp.add_forced_key_binding("f", "search_string_f", function()
+ update_search_results("f")
+ end, "repeatable")
+ mp.add_forced_key_binding("g", "search_string_g", function()
+ update_search_results("g")
+ end, "repeatable")
+ mp.add_forced_key_binding("h", "search_string_h", function()
+ update_search_results("h")
+ end, "repeatable")
+ mp.add_forced_key_binding("i", "search_string_i", function()
+ update_search_results("i")
+ end, "repeatable")
+ mp.add_forced_key_binding("j", "search_string_j", function()
+ update_search_results("j")
+ end, "repeatable")
+ mp.add_forced_key_binding("k", "search_string_k", function()
+ update_search_results("k")
+ end, "repeatable")
+ mp.add_forced_key_binding("l", "search_string_l", function()
+ update_search_results("l")
+ end, "repeatable")
+ mp.add_forced_key_binding("m", "search_string_m", function()
+ update_search_results("m")
+ end, "repeatable")
+ mp.add_forced_key_binding("n", "search_string_n", function()
+ update_search_results("n")
+ end, "repeatable")
+ mp.add_forced_key_binding("o", "search_string_o", function()
+ update_search_results("o")
+ end, "repeatable")
+ mp.add_forced_key_binding("p", "search_string_p", function()
+ update_search_results("p")
+ end, "repeatable")
+ mp.add_forced_key_binding("q", "search_string_q", function()
+ update_search_results("q")
+ end, "repeatable")
+ mp.add_forced_key_binding("r", "search_string_r", function()
+ update_search_results("r")
+ end, "repeatable")
+ mp.add_forced_key_binding("s", "search_string_s", function()
+ update_search_results("s")
+ end, "repeatable")
+ mp.add_forced_key_binding("t", "search_string_t", function()
+ update_search_results("t")
+ end, "repeatable")
+ mp.add_forced_key_binding("u", "search_string_u", function()
+ update_search_results("u")
+ end, "repeatable")
+ mp.add_forced_key_binding("v", "search_string_v", function()
+ update_search_results("v")
+ end, "repeatable")
+ mp.add_forced_key_binding("w", "search_string_w", function()
+ update_search_results("w")
+ end, "repeatable")
+ mp.add_forced_key_binding("x", "search_string_x", function()
+ update_search_results("x")
+ end, "repeatable")
+ mp.add_forced_key_binding("y", "search_string_y", function()
+ update_search_results("y")
+ end, "repeatable")
+ mp.add_forced_key_binding("z", "search_string_z", function()
+ update_search_results("z")
+ end, "repeatable")
+
+ mp.add_forced_key_binding("A", "search_string_A", function()
+ update_search_results("A")
+ end, "repeatable")
+ mp.add_forced_key_binding("B", "search_string_B", function()
+ update_search_results("B")
+ end, "repeatable")
+ mp.add_forced_key_binding("C", "search_string_C", function()
+ update_search_results("C")
+ end, "repeatable")
+ mp.add_forced_key_binding("D", "search_string_D", function()
+ update_search_results("D")
+ end, "repeatable")
+ mp.add_forced_key_binding("E", "search_string_E", function()
+ update_search_results("E")
+ end, "repeatable")
+ mp.add_forced_key_binding("F", "search_string_F", function()
+ update_search_results("F")
+ end, "repeatable")
+ mp.add_forced_key_binding("G", "search_string_G", function()
+ update_search_results("G")
+ end, "repeatable")
+ mp.add_forced_key_binding("H", "search_string_H", function()
+ update_search_results("H")
+ end, "repeatable")
+ mp.add_forced_key_binding("I", "search_string_I", function()
+ update_search_results("I")
+ end, "repeatable")
+ mp.add_forced_key_binding("J", "search_string_J", function()
+ update_search_results("J")
+ end, "repeatable")
+ mp.add_forced_key_binding("K", "search_string_K", function()
+ update_search_results("K")
+ end, "repeatable")
+ mp.add_forced_key_binding("L", "search_string_L", function()
+ update_search_results("L")
+ end, "repeatable")
+ mp.add_forced_key_binding("M", "search_string_M", function()
+ update_search_results("M")
+ end, "repeatable")
+ mp.add_forced_key_binding("N", "search_string_N", function()
+ update_search_results("N")
+ end, "repeatable")
+ mp.add_forced_key_binding("O", "search_string_O", function()
+ update_search_results("O")
+ end, "repeatable")
+ mp.add_forced_key_binding("P", "search_string_P", function()
+ update_search_results("P")
+ end, "repeatable")
+ mp.add_forced_key_binding("Q", "search_string_Q", function()
+ update_search_results("Q")
+ end, "repeatable")
+ mp.add_forced_key_binding("R", "search_string_R", function()
+ update_search_results("R")
+ end, "repeatable")
+ mp.add_forced_key_binding("S", "search_string_S", function()
+ update_search_results("S")
+ end, "repeatable")
+ mp.add_forced_key_binding("T", "search_string_T", function()
+ update_search_results("T")
+ end, "repeatable")
+ mp.add_forced_key_binding("U", "search_string_U", function()
+ update_search_results("U")
+ end, "repeatable")
+ mp.add_forced_key_binding("V", "search_string_V", function()
+ update_search_results("V")
+ end, "repeatable")
+ mp.add_forced_key_binding("W", "search_string_W", function()
+ update_search_results("W")
+ end, "repeatable")
+ mp.add_forced_key_binding("X", "search_string_X", function()
+ update_search_results("X")
+ end, "repeatable")
+ mp.add_forced_key_binding("Y", "search_string_Y", function()
+ update_search_results("Y")
+ end, "repeatable")
+ mp.add_forced_key_binding("Z", "search_string_Z", function()
+ update_search_results("Z")
+ end, "repeatable")
+
+ mp.add_forced_key_binding("1", "search_string_1", function()
+ update_search_results("1")
+ end, "repeatable")
+ mp.add_forced_key_binding("2", "search_string_2", function()
+ update_search_results("2")
+ end, "repeatable")
+ mp.add_forced_key_binding("3", "search_string_3", function()
+ update_search_results("3")
+ end, "repeatable")
+ mp.add_forced_key_binding("4", "search_string_4", function()
+ update_search_results("4")
+ end, "repeatable")
+ mp.add_forced_key_binding("5", "search_string_5", function()
+ update_search_results("5")
+ end, "repeatable")
+ mp.add_forced_key_binding("6", "search_string_6", function()
+ update_search_results("6")
+ end, "repeatable")
+ mp.add_forced_key_binding("7", "search_string_7", function()
+ update_search_results("7")
+ end, "repeatable")
+ mp.add_forced_key_binding("8", "search_string_8", function()
+ update_search_results("8")
+ end, "repeatable")
+ mp.add_forced_key_binding("9", "search_string_9", function()
+ update_search_results("9")
+ end, "repeatable")
+ mp.add_forced_key_binding("0", "search_string_0", function()
+ update_search_results("0")
+ end, "repeatable")
+
+ mp.add_forced_key_binding("SPACE", "search_string_space", function()
+ update_search_results(" ")
+ end, "repeatable")
+ mp.add_forced_key_binding("`", "search_string_`", function()
+ update_search_results("`")
+ end, "repeatable")
+ mp.add_forced_key_binding("~", "search_string_~", function()
+ update_search_results("~")
+ end, "repeatable")
+ mp.add_forced_key_binding("!", "search_string_!", function()
+ update_search_results("!")
+ end, "repeatable")
+ mp.add_forced_key_binding("@", "search_string_@", function()
+ update_search_results("@")
+ end, "repeatable")
+ mp.add_forced_key_binding("SHARP", "search_string_sharp", function()
+ update_search_results("#")
+ end, "repeatable")
+ mp.add_forced_key_binding("$", "search_string_$", function()
+ update_search_results("$")
+ end, "repeatable")
+ mp.add_forced_key_binding("%", "search_string_percentage", function()
+ update_search_results("%")
+ end, "repeatable")
+ mp.add_forced_key_binding("^", "search_string_^", function()
+ update_search_results("^")
+ end, "repeatable")
+ mp.add_forced_key_binding("&", "search_string_&", function()
+ update_search_results("&")
+ end, "repeatable")
+ mp.add_forced_key_binding("*", "search_string_*", function()
+ update_search_results("*")
+ end, "repeatable")
+ mp.add_forced_key_binding("(", "search_string_(", function()
+ update_search_results("(")
+ end, "repeatable")
+ mp.add_forced_key_binding(")", "search_string_)", function()
+ update_search_results(")")
+ end, "repeatable")
+ mp.add_forced_key_binding("-", "search_string_-", function()
+ update_search_results("-")
+ end, "repeatable")
+ mp.add_forced_key_binding("_", "search_string__", function()
+ update_search_results("_")
+ end, "repeatable")
+ mp.add_forced_key_binding("=", "search_string_=", function()
+ update_search_results("=")
+ end, "repeatable")
+ mp.add_forced_key_binding("+", "search_string_+", function()
+ update_search_results("+")
+ end, "repeatable")
+ mp.add_forced_key_binding("\\", "search_string_\\", function()
+ update_search_results("\\")
+ end, "repeatable")
+ mp.add_forced_key_binding("|", "search_string_|", function()
+ update_search_results("|")
+ end, "repeatable")
+ mp.add_forced_key_binding("]", "search_string_]", function()
+ update_search_results("]")
+ end, "repeatable")
+ mp.add_forced_key_binding("}", "search_string_rightcurly", function()
+ update_search_results("}")
+ end, "repeatable")
+ mp.add_forced_key_binding("[", "search_string_[", function()
+ update_search_results("[")
+ end, "repeatable")
+ mp.add_forced_key_binding("{", "search_string_leftcurly", function()
+ update_search_results("{")
+ end, "repeatable")
+ mp.add_forced_key_binding("'", "search_string_'", function()
+ update_search_results("'")
+ end, "repeatable")
+ mp.add_forced_key_binding('"', 'search_string_"', function()
+ update_search_results('"')
+ end, "repeatable")
+ mp.add_forced_key_binding(";", "search_string_semicolon", function()
+ update_search_results(";")
+ end, "repeatable")
+ mp.add_forced_key_binding(":", "search_string_:", function()
+ update_search_results(":")
+ end, "repeatable")
+ mp.add_forced_key_binding("/", "search_string_/", function()
+ update_search_results("/")
+ end, "repeatable")
+ mp.add_forced_key_binding("?", "search_string_?", function()
+ update_search_results("?")
+ end, "repeatable")
+ mp.add_forced_key_binding(".", "search_string_.", function()
+ update_search_results(".")
+ end, "repeatable")
+ mp.add_forced_key_binding(">", "search_string_>", function()
+ update_search_results(">")
+ end, "repeatable")
+ mp.add_forced_key_binding(",", "search_string_,", function()
+ update_search_results(",")
+ end, "repeatable")
+ mp.add_forced_key_binding("<", "search_string_<", function()
+ update_search_results("<")
+ end, "repeatable")
+
+ mp.add_forced_key_binding("bs", "search_string_del", function()
+ update_search_results("", "string_del")
+ end, "repeatable")
+ bind_keys(o.list_close_keybind, "search_exit", function()
+ list_search_exit()
+ end)
+ bind_keys(o.list_search_not_typing_mode_keybind, "search_string_not_typing", function()
+ list_search_not_typing_mode(false)
+ end)
+
+ if o.search_not_typing_smartly then
+ bind_keys(o.next_filter_sequence_keybind, "list-filter-next", function()
+ list_filter_next()
+ list_search_not_typing_mode(true)
+ end)
+ bind_keys(o.previous_filter_sequence_keybind, "list-filter-previous", function()
+ list_filter_previous()
+ list_search_not_typing_mode(true)
+ end)
+ bind_keys(o.list_delete_keybind, "list-delete", function()
+ list_delete()
+ list_search_not_typing_mode(true)
+ end)
+ bind_keys(o.list_delete_highlighted_keybind, "list-delete-highlight", function()
+ list_delete("highlight")
+ list_search_not_typing_mode(true)
+ end)
+ end
+end
+
+function unbind_search_keys()
+ mp.remove_key_binding("search_string_a")
+ mp.remove_key_binding("search_string_b")
+ mp.remove_key_binding("search_string_c")
+ mp.remove_key_binding("search_string_d")
+ mp.remove_key_binding("search_string_e")
+ mp.remove_key_binding("search_string_f")
+ mp.remove_key_binding("search_string_g")
+ mp.remove_key_binding("search_string_h")
+ mp.remove_key_binding("search_string_i")
+ mp.remove_key_binding("search_string_j")
+ mp.remove_key_binding("search_string_k")
+ mp.remove_key_binding("search_string_l")
+ mp.remove_key_binding("search_string_m")
+ mp.remove_key_binding("search_string_n")
+ mp.remove_key_binding("search_string_o")
+ mp.remove_key_binding("search_string_p")
+ mp.remove_key_binding("search_string_q")
+ mp.remove_key_binding("search_string_r")
+ mp.remove_key_binding("search_string_s")
+ mp.remove_key_binding("search_string_t")
+ mp.remove_key_binding("search_string_u")
+ mp.remove_key_binding("search_string_v")
+ mp.remove_key_binding("search_string_w")
+ mp.remove_key_binding("search_string_x")
+ mp.remove_key_binding("search_string_y")
+ mp.remove_key_binding("search_string_z")
+
+ mp.remove_key_binding("search_string_A")
+ mp.remove_key_binding("search_string_B")
+ mp.remove_key_binding("search_string_C")
+ mp.remove_key_binding("search_string_D")
+ mp.remove_key_binding("search_string_E")
+ mp.remove_key_binding("search_string_F")
+ mp.remove_key_binding("search_string_G")
+ mp.remove_key_binding("search_string_H")
+ mp.remove_key_binding("search_string_I")
+ mp.remove_key_binding("search_string_J")
+ mp.remove_key_binding("search_string_K")
+ mp.remove_key_binding("search_string_L")
+ mp.remove_key_binding("search_string_M")
+ mp.remove_key_binding("search_string_N")
+ mp.remove_key_binding("search_string_O")
+ mp.remove_key_binding("search_string_P")
+ mp.remove_key_binding("search_string_Q")
+ mp.remove_key_binding("search_string_R")
+ mp.remove_key_binding("search_string_S")
+ mp.remove_key_binding("search_string_T")
+ mp.remove_key_binding("search_string_U")
+ mp.remove_key_binding("search_string_V")
+ mp.remove_key_binding("search_string_W")
+ mp.remove_key_binding("search_string_X")
+ mp.remove_key_binding("search_string_Y")
+ mp.remove_key_binding("search_string_Z")
+
+ mp.remove_key_binding("search_string_1")
+ mp.remove_key_binding("search_string_2")
+ mp.remove_key_binding("search_string_3")
+ mp.remove_key_binding("search_string_4")
+ mp.remove_key_binding("search_string_5")
+ mp.remove_key_binding("search_string_6")
+ mp.remove_key_binding("search_string_7")
+ mp.remove_key_binding("search_string_8")
+ mp.remove_key_binding("search_string_9")
+ mp.remove_key_binding("search_string_0")
+
+ mp.remove_key_binding("search_string_space")
+ mp.remove_key_binding("search_string_`")
+ mp.remove_key_binding("search_string_~")
+ mp.remove_key_binding("search_string_!")
+ mp.remove_key_binding("search_string_@")
+ mp.remove_key_binding("search_string_sharp")
+ mp.remove_key_binding("search_string_$")
+ mp.remove_key_binding("search_string_percentage")
+ mp.remove_key_binding("search_string_^")
+ mp.remove_key_binding("search_string_&")
+ mp.remove_key_binding("search_string_*")
+ mp.remove_key_binding("search_string_(")
+ mp.remove_key_binding("search_string_)")
+ mp.remove_key_binding("search_string_-")
+ mp.remove_key_binding("search_string__")
+ mp.remove_key_binding("search_string_=")
+ mp.remove_key_binding("search_string_+")
+ mp.remove_key_binding("search_string_\\")
+ mp.remove_key_binding("search_string_|")
+ mp.remove_key_binding("search_string_]")
+ mp.remove_key_binding("search_string_rightcurly")
+ mp.remove_key_binding("search_string_[")
+ mp.remove_key_binding("search_string_leftcurly")
+ mp.remove_key_binding("search_string_'")
+ mp.remove_key_binding('search_string_"')
+ mp.remove_key_binding("search_string_semicolon")
+ mp.remove_key_binding("search_string_:")
+ mp.remove_key_binding("search_string_/")
+ mp.remove_key_binding("search_string_?")
+ mp.remove_key_binding("search_string_.")
+ mp.remove_key_binding("search_string_>")
+ mp.remove_key_binding("search_string_,")
+ mp.remove_key_binding("search_string_<")
+
+ mp.remove_key_binding("search_string_del")
+ if not search_active then
+ unbind_keys(o.list_close_keybind, "search_exit")
+ end
+end
+--End of LogManager Search Feature--
+---------End of LogManager---------
+
+function mark_chapter()
+ if not o.mark_clipboard_as_chapter then
+ return
+ end
+
+ local all_chapters = mp.get_property_native("chapter-list")
+ local chapter_index = 0
+ local chapters_time = {}
+
+ get_list_contents()
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ for i = 1, #list_contents do
+ if list_contents[i].found_path == filePath and tonumber(list_contents[i].found_time) > 0 then
+ table.insert(chapters_time, tonumber(list_contents[i].found_time))
+ end
+ end
+ if not chapters_time[1] then
+ return
+ end
+
+ table.sort(chapters_time, function(a, b)
+ return a < b
+ end)
+
+ for i = 1, #chapters_time do
+ chapter_index = chapter_index + 1
+
+ all_chapters[chapter_index] = {
+ title = "SmartCopyPaste-II " .. chapter_index,
+ time = chapters_time[i],
+ }
+ end
+
+ table.sort(all_chapters, function(a, b)
+ return a["time"] < b["time"]
+ end)
+
+ mp.set_property_native("chapter-list", all_chapters)
+end
+
+function write_log(target_time, update_seekTime, entry_limit, action)
+ if not filePath then
+ return
+ end
+ local prev_seekTime = seekTime
+ seekTime = (mp.get_property_number("time-pos") or 0)
+ if target_time then
+ seekTime = target_time
+ end
+ if seekTime < 0 then
+ seekTime = 0
+ end
+
+ delete_log_entry(false, true, filePath, math.floor(seekTime), entry_limit)
+
+ local f = io.open(log_fullpath, "a+")
+ if o.file_title_logging == "all" then
+ f:write(
+ ('[%s] "%s" | %s | %s | %s'):format(
+ os.date(o.date_format),
+ fileTitle,
+ filePath,
+ log_length_text .. tostring(fileLength),
+ log_time_text .. tostring(seekTime)
+ )
+ )
+ elseif o.file_title_logging == "protocols" and (starts_protocol(o.logging_protocols, filePath)) then
+ f:write(
+ ('[%s] "%s" | %s | %s | %s'):format(
+ os.date(o.date_format),
+ fileTitle,
+ filePath,
+ log_length_text .. tostring(fileLength),
+ log_time_text .. tostring(seekTime)
+ )
+ )
+ elseif o.file_title_logging == "protocols" and not (starts_protocol(o.logging_protocols, filePath)) then
+ f:write(
+ ("[%s] %s | %s | %s"):format(
+ os.date(o.date_format),
+ filePath,
+ log_length_text .. tostring(fileLength),
+ log_time_text .. tostring(seekTime)
+ )
+ )
+ else
+ f:write(
+ ("[%s] %s | %s | %s"):format(
+ os.date(o.date_format),
+ filePath,
+ log_length_text .. tostring(fileLength),
+ log_time_text .. tostring(seekTime)
+ )
+ )
+ end
+
+ if action == "copy" then
+ f:write(" | " .. log_clipboard_text .. action)
+ end
+ if action == "paste" then
+ f:write(" | " .. log_clipboard_text .. action)
+ end
+
+ f:write("\n")
+ f:close()
+
+ if not update_seekTime then
+ seekTime = prev_seekTime
+ end
+end
+
+----- SmartCopyPaste Specific Code -----
+
+table.insert(o.pastable_time_attributes, o.protocols_time_attribute)
+table.insert(o.pastable_time_attributes, o.local_time_attribute)
+for i = 1, #o.specific_time_attributes do
+ if not has_value(o.pastable_time_attributes, o.specific_time_attributes[i][2]) then
+ table.insert(o.pastable_time_attributes, o.specific_time_attributes[i][2])
+ end
+end
+
+local clip, clip_time, clip_file
+local clipboard_pasted = false
+
+if not o.device or o.device == "auto" then
+ if os.getenv("windir") ~= nil then
+ o.device = "windows"
+ elseif
+ os.execute('[ -d "/Applications" ]') == 0 and os.execute('[ -d "/Library" ]') == 0
+ or os.execute('[ -d "/Applications" ]') == true and os.execute('[ -d "/Library" ]') == true
+ then
+ o.device = "mac"
+ else
+ o.device = "linux"
+ end
+end
+
+function handleres(res, args)
+ if not res.error and res.status == 0 then
+ return res.stdout
+ else
+ msg.error("There was an error getting " .. o.device .. " clipboard: ")
+ msg.error(" Status: " .. (res.status or ""))
+ msg.error(" Error: " .. (res.error or ""))
+ msg.error(" stdout: " .. (res.stdout or ""))
+ msg.error("args: " .. utils.to_string(args))
+ return ""
+ end
+end
+
+function os.capture(cmd)
+ local f = assert(io.popen(cmd, "r"))
+ local s = assert(f:read("*a"))
+ f:close()
+ return s
+end
+
+function make_raw(s)
+ if not s then
+ return
+ end
+ s = string.gsub(s, "^%s+", "")
+ s = string.gsub(s, "%s+$", "")
+ s = string.gsub(s, "[\n\r]+", " ")
+ return s
+end
+
+function get_extension(path)
+ if not path then
+ return
+ end
+
+ match = string.match(path, "%.([^%.]+)$")
+ if match == nil then
+ return "nomatch"
+ else
+ return match
+ end
+end
+
+function get_specific_attribute(target_path)
+ local pre_attribute = ""
+ local after_attribute = ""
+ if not starts_protocol(protocols, target_path) then
+ pre_attribute = o.local_time_attribute
+ elseif starts_protocol(protocols, target_path) then
+ pre_attribute = o.protocols_time_attribute
+ for i = 1, #o.specific_time_attributes do
+ if contain_value({ o.specific_time_attributes[i][1] }, target_path) then
+ pre_attribute = o.specific_time_attributes[i][2]
+ after_attribute = o.specific_time_attributes[i][3]
+ break
+ end
+ end
+ end
+ return pre_attribute, after_attribute
+end
+
+function get_time_attribute(target_path)
+ local pre_attribute = ""
+ for i = 1, #o.pastable_time_attributes do
+ if contain_value({ o.pastable_time_attributes[i] }, target_path) then
+ pre_attribute = o.pastable_time_attributes[i]
+ break
+ end
+ end
+ return pre_attribute
+end
+
+function get_clipboard()
+ local clipboard
+ if o.device == "linux" then
+ clipboard = os.capture(o.linux_paste)
+ return clipboard
+ elseif o.device == "windows" then
+ if o.windows_paste == "powershell" then
+ local args = {
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ [[& {
+ Trap {
+ Write-Error -ErrorRecord $_
+ Exit 1
+ }
+ $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
+ if (-not $clip) {
+ $clip = Get-Clipboard -Raw -Format FileDropList
+ }
+ $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
+ [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
+ }]],
+ }
+ return handleres(utils.subprocess({ args = args, cancellable = false }), args)
+ else
+ clipboard = os.capture(o.windows_paste)
+ return clipboard
+ end
+ elseif o.device == "mac" then
+ clipboard = os.capture(o.mac_paste)
+ return clipboard
+ end
+ return ""
+end
+
+function set_clipboard(text)
+ local pipe
+ if o.device == "linux" then
+ pipe = io.popen(o.linux_copy, "w")
+ pipe:write(text)
+ pipe:close()
+ elseif o.device == "windows" then
+ if o.windows_copy == "powershell" then
+ local res = utils.subprocess({
+ args = {
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ string.format(
+ [[& {
+ Trap {
+ Write-Error -ErrorRecord $_
+ Exit 1
+ }
+ Add-Type -AssemblyName PresentationCore
+ [System.Windows.Clipboard]::SetText('%s')
+ }]],
+ text
+ ),
+ },
+ })
+ else
+ pipe = io.popen(o.windows_copy, "w")
+ pipe:write(text)
+ pipe:close()
+ end
+ elseif o.device == "mac" then
+ pipe = io.popen(o.mac_copy, "w")
+ pipe:write(text)
+ pipe:close()
+ end
+ return ""
+end
+
+function parse_clipboard(text)
+ if not text then
+ return
+ end
+
+ local clip, clip_file, clip_time, pre_attribute
+ local clip_table = {}
+ clip = text
+
+ for c in clip:gmatch("[^\n\r]+") do --3.2.1# fix for #80 , accidentally additional "+" was added to the gmatch
+ local c_pre_attribute, c_clip_file, c_clip_time, c_clip_extension
+ c = make_raw(c)
+
+ if starts_protocol(protocols, c) then --3.2# handle protocols to allow for space as a seperator
+ for c_protocols in c:gmatch("[^%s]+") do --3.2# loop iterator using space
+ if starts_protocol(protocols, c_protocols) then --3.2# check if it starts with protocols again after a space
+ c_pre_attribute = get_time_attribute(c)
+ if string.match(c, "(.*)" .. c_pre_attribute) then
+ c_clip_file = string.match(c_protocols, "(.*)" .. c_pre_attribute)
+ c_clip_time = tonumber(string.match(c_protocols, c_pre_attribute .. "(%d*%.?%d*)"))
+ elseif string.match(c, '^"(.*)"$') then
+ c_clip_file = string.match(c, '^"(.*)"$')
+ else
+ c_clip_file = c_protocols
+ end
+ c_clip_extension = get_extension(c_clip_file)
+ table.insert(clip_table, { c_clip_file, c_clip_time, c_clip_extension })
+ end
+ end
+ else --3.2# otherwise continue as usual with new line seperators only
+ c_pre_attribute = get_time_attribute(c)
+ if string.match(c, "(.*)" .. c_pre_attribute) then
+ c_clip_file = string.match(c, "(.*)" .. c_pre_attribute)
+ c_clip_time = tonumber(string.match(c, c_pre_attribute .. "(%d*%.?%d*)"))
+ elseif string.match(c, '^"(.*)"$') then
+ c_clip_file = string.match(c, '^"(.*)"$')
+ else
+ c_clip_file = c
+ end
+
+ c_clip_extension = get_extension(c_clip_file)
+ table.insert(clip_table, { c_clip_file, c_clip_time, c_clip_extension })
+ end
+ end
+
+ clip = make_raw(clip)
+ pre_attribute = get_time_attribute(clip)
+
+ if string.match(clip, "(.*)" .. pre_attribute) then
+ clip_file = string.match(clip, "(.*)" .. pre_attribute)
+ clip_time = tonumber(string.match(clip, pre_attribute .. "(%d*%.?%d*)"))
+ elseif string.match(clip, '^"(.*)"$') then
+ clip_file = string.match(clip, '^"(.*)"$')
+ else
+ clip_file = clip
+ end
+
+ return clip, clip_file, clip_time, clip_table
+end
+
+function copy()
+ if filePath ~= nil then
+ if o.copy_time_method == "none" or copy_time_method == "" then
+ copy_specific("path")
+ return
+ elseif o.copy_time_method == "protocols" and not starts_protocol(protocols, filePath) then
+ copy_specific("path")
+ return
+ elseif o.copy_time_method == "local" and starts_protocol(protocols, filePath) then
+ copy_specific("path")
+ return
+ elseif o.copy_time_method == "specifics" then
+ if not starts_protocol(protocols, filePath) then
+ copy_specific("path")
+ return
+ else
+ for i = 1, #o.specific_time_attributes do
+ if contain_value({ o.specific_time_attributes[i][1] }, filePath) then
+ copy_specific("path&timestamp")
+ return
+ end
+ end
+ copy_specific("path")
+ return
+ end
+ else
+ copy_specific("path&timestamp")
+ return
+ end
+ else
+ if o.osd_messages == true then
+ mp.osd_message("Failed to Copy\nNo Video Found")
+ end
+ msg.info("Failed to copy, no video found")
+ end
+end
+
+function copy_specific(action)
+ if not action then
+ return
+ end
+
+ if filePath == nil then
+ if o.osd_messages == true then
+ mp.osd_message("Failed to Copy\nNo Video Found")
+ end
+ msg.info("Failed to copy, no video found")
+ return
+ else
+ if action == "title" then
+ if o.osd_messages == true then
+ mp.osd_message("Copied:\n" .. fileTitle)
+ end
+ set_clipboard(fileTitle)
+ msg.info("Copied the below into clipboard:\n" .. fileTitle)
+ end
+ if action == "path" then
+ if o.osd_messages == true then
+ mp.osd_message("Copied:\n" .. filePath)
+ end
+ set_clipboard(filePath)
+ msg.info("Copied and logged the below into clipboard:\n" .. filePath)
+ write_log(0, false, o.same_entry_limit, "copy")
+ end
+ if action == "timestamp" then
+ local pre_attribute, after_attribute = get_specific_attribute(filePath)
+ local video_time = mp.get_property_number("time-pos")
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Copied"
+ .. o.time_seperator
+ .. format_time(video_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ set_clipboard(
+ pre_attribute
+ .. format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])
+ .. after_attribute
+ )
+ msg.info(
+ "Copied the below into clipboard:\n"
+ .. pre_attribute
+ .. format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])
+ .. after_attribute
+ )
+ end
+ if action == "path&timestamp" then
+ local pre_attribute, after_attribute = get_specific_attribute(filePath)
+ local video_time = mp.get_property_number("time-pos")
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Copied:\n"
+ .. fileTitle
+ .. o.time_seperator
+ .. format_time(video_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ set_clipboard(
+ filePath
+ .. pre_attribute
+ .. format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])
+ .. after_attribute
+ )
+ msg.info(
+ "Copied and logged the below into clipboard:\n"
+ .. filePath
+ .. pre_attribute
+ .. format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])
+ .. after_attribute
+ )
+ write_log(false, false, o.same_entry_limit, "copy")
+ end
+ end
+end
+
+function trigger_paste_action(action)
+ if not action then
+ return
+ end
+
+ if action == "load-file" then
+ filePath = clip_file
+ if o.osd_messages == true then
+ if clip_time ~= nil then
+ mp.osd_message(
+ "Pasted:\n"
+ .. clip_file
+ .. o.time_seperator
+ .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ else
+ mp.osd_message("Pasted:\n" .. clip_file)
+ end
+ end
+ mp.commandv("loadfile", clip_file)
+ clipboard_pasted = true
+
+ if clip_time ~= nil then
+ msg.info("Pasted the below file into mpv:\n" .. clip_file .. format_time(clip_time))
+ else
+ msg.info("Pasted the below file into mpv:\n" .. clip_file)
+ end
+ end
+
+ if action == "load-subtitle" then
+ if o.osd_messages == true then
+ mp.osd_message("Pasted Subtitle:\n" .. clip_file)
+ end
+ mp.commandv("sub-add", clip_file, "select")
+ msg.info("Pasted the below subtitle into mpv:\n" .. clip_file)
+ end
+
+ if action == "file-seek" then
+ local video_duration = mp.get_property_number("duration")
+ seekTime = clip_time + o.resume_offset
+
+ if seekTime > video_duration then
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Time Paste Exceeds Video Length"
+ .. o.time_seperator
+ .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ msg.info("The time pasted exceeds the video length:\n" .. format_time(clip_time))
+ return
+ end
+
+ if seekTime < 0 then
+ seekTime = 0
+ end
+
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Resumed to Pasted Time"
+ .. o.time_seperator
+ .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ mp.commandv("seek", seekTime, "absolute", "exact")
+ msg.info("Resumed to the pasted time" .. o.time_seperator .. format_time(clip_time))
+ end
+
+ if action == "add-playlist" then
+ if o.osd_messages == true then
+ mp.osd_message("Pasted Into Playlist:\n" .. clip_file)
+ end
+ mp.commandv("loadfile", clip_file, "append-play")
+ msg.info("Pasted the below into playlist and added it to the log file:\n" .. clip_file)
+
+ local temp_filePath = filePath
+ local temp_title_logging = o.file_title_logging
+
+ filePath = clip_file
+ o.file_title_logging = "none"
+ write_log(0, false, o.same_entry_limit, "paste")
+ filePath = temp_filePath
+ o.file_title_logging = temp_title_logging
+ end
+
+ if action == "log-force" then
+ get_list_contents("all", "added-asc")
+ load(1)
+ if seekTime > 0 then
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Pasted From Log:\n"
+ .. list_contents[#list_contents - 1 + 1].found_path
+ .. o.time_seperator
+ .. format_time(
+ list_contents[#list_contents - 1 + 1].found_time,
+ o.osd_time_format[3],
+ o.osd_time_format[2],
+ o.osd_time_format[1]
+ )
+ )
+ end
+ msg.info(
+ "Pasted the below from log file into mpv:\n"
+ .. list_contents[#list_contents - 1 + 1].found_path
+ .. o.time_seperator
+ .. format_time(list_contents[#list_contents - 1 + 1].found_time)
+ )
+ else
+ if o.osd_messages == true then
+ mp.osd_message("Pasted From Log:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ end
+ msg.info("Pasted the below from log file into mpv:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ end
+ end
+
+ if action == "log-force-noresume" then
+ get_list_contents("all", "added-asc")
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ load(1, false, 0)
+ if o.osd_messages == true then
+ mp.osd_message("Pasted From Log:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ end
+ msg.info("Pasted the below from log file into mpv:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ end
+
+ if action == "log-playlist" then
+ get_list_contents("all", "added-asc")
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ load(1, true)
+ if o.osd_messages == true then
+ mp.osd_message("Pasted From Log To Playlist:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ end
+ msg.info(
+ "Pasted the below from log file into mpv playlist:\n" .. list_contents[#list_contents - 1 + 1].found_path
+ )
+ end
+
+ if action == "log-timestamp" then
+ get_list_contents("all", "added-asc")
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ local log_time = 0
+ for i = #list_contents, 1, -1 do
+ if list_contents[i].found_path == filePath and tonumber(list_contents[i].found_time) > 0 then
+ log_time = tonumber(list_contents[i].found_time) + o.resume_offset
+ break
+ end
+ end
+ if log_time > 0 then
+ mp.commandv("seek", log_time, "absolute", "exact")
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Pasted Time From Log"
+ .. o.time_seperator
+ .. format_time(log_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ msg.info("Pasted resume time of video from the log file: " .. format_time(log_time))
+ else
+ list_contents = nil
+ end
+ end
+
+ if action == "log-timestamp>playlist" then
+ get_list_contents("all", "added-asc")
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ local log_time = 0
+ for i = #list_contents, 1, -1 do
+ if list_contents[i].found_path == filePath and tonumber(list_contents[i].found_time) > 0 then
+ log_time = tonumber(list_contents[i].found_time) + o.resume_offset
+ break
+ end
+ end
+ if log_time > 0 then
+ mp.commandv("seek", log_time, "absolute", "exact")
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Pasted Time From Log"
+ .. o.time_seperator
+ .. format_time(log_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ msg.info("Pasted resume time of video from the log file: " .. format_time(log_time))
+ else
+ load(1, true)
+ mp.osd_message("Pasted From Log To Playlist:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ msg.info(
+ "Pasted the below from log file into mpv playlist:\n"
+ .. list_contents[#list_contents - 1 + 1].found_path
+ )
+ end
+ end
+
+ if action == "log-timestamp>force" then
+ get_list_contents("all", "added-asc")
+ if not list_contents or not list_contents[1] then
+ return
+ end
+ local log_time = 0
+ for i = #list_contents, 1, -1 do
+ if list_contents[i].found_path == filePath and tonumber(list_contents[i].found_time) > 0 then
+ log_time = tonumber(list_contents[i].found_time) + o.resume_offset
+ break
+ end
+ end
+ if log_time > 0 then
+ mp.commandv("seek", log_time, "absolute", "exact")
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Pasted Time From Log"
+ .. o.time_seperator
+ .. format_time(log_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ msg.info("Pasted resume time of video from the log file: " .. format_time(log_time))
+ else
+ load(1)
+ if seekTime > 0 then
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Pasted From Log:\n"
+ .. list_contents[#list_contents - 1 + 1].found_path
+ .. o.time_seperator
+ .. format_time(
+ list_contents[#list_contents - 1 + 1].found_time,
+ o.osd_time_format[3],
+ o.osd_time_format[2],
+ o.osd_time_format[1]
+ )
+ )
+ end
+ msg.info(
+ "Pasted the below from log file into mpv:\n"
+ .. list_contents[#list_contents - 1 + 1].found_path
+ .. o.time_seperator
+ .. format_time(list_contents[#list_contents - 1 + 1].found_time)
+ )
+ else
+ if o.osd_messages == true then
+ mp.osd_message("Pasted From Log:\n" .. list_contents[#list_contents - 1 + 1].found_path)
+ end
+ msg.info(
+ "Pasted the below from log file into mpv:\n" .. list_contents[#list_contents - 1 + 1].found_path
+ )
+ end
+ end
+ end
+
+ if action == "error-subtitle" then
+ if o.osd_messages == true then
+ mp.osd_message("Subtitle Paste Requires Running Video:\n" .. clip_file)
+ end
+ msg.info("Subtitles can only be pasted if a video is running:\n" .. clip_file)
+ end
+
+ if action == "error-unsupported" then
+ if o.osd_messages == true then
+ mp.osd_message("Paste of this item is unsupported possibly due to configuration:\n" .. clip)
+ end
+ msg.info(
+ "Failed to paste into mpv, pasted item shown below is unsupported possibly due to configuration:\n" .. clip
+ )
+ end
+
+ if action == "error-missing" then
+ if o.osd_messages == true then
+ mp.osd_message("File Doesn't Exist:\n" .. clip_file)
+ end
+ msg.info("The file below doesn't seem to exist:\n" .. clip_file)
+ end
+
+ if action == "error-time" then
+ if o.osd_messages == true then
+ if clip_time ~= nil then
+ mp.osd_message(
+ "Time Paste Requires Running Video"
+ .. o.time_seperator
+ .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ else
+ mp.osd_message("Time Paste Requires Running Video")
+ end
+ end
+
+ if clip_time ~= nil then
+ msg.info("Time can only be pasted if a video is running:\n" .. format_time(clip_time))
+ else
+ msg.info("Time can only be pasted if a video is running")
+ end
+ end
+
+ if action == "error-missingtime" then
+ if o.osd_messages == true then
+ mp.osd_message("Clipboard does not contain time for seeking:\n" .. clip)
+ end
+ msg.info("Clipboard does not contain the time attribute and time for seeking:\n" .. clip)
+ end
+
+ if action == "error-samefile" then
+ if o.osd_messages == true then
+ mp.osd_message("Pasted file is already running:\n" .. clip)
+ end
+ msg.info("Pasted file shown below is already running:\n" .. clip)
+ end
+
+ if action == "error-unknown" then
+ if o.osd_messages == true then
+ mp.osd_message("Paste was ignored due to an error:\n" .. clip)
+ end
+ msg.info("Paste was ignored due to an error:\n" .. clip)
+ end
+end
+
+function multipaste()
+ if #clip_table < 2 then
+ return msg.warn("Single paste should be called instead of multipaste")
+ end
+ local file_ignored_total = 0
+ local file_subtitle_total = 0
+ local triggered_multipaste = {}
+
+ if filePath == nil then
+ for i = 1, #clip_table do
+ if
+ file_exists(clip_table[i][1]) and has_value(o.paste_extensions, clip_table[i][3])
+ or starts_protocol(o.paste_protocols, clip_table[i][1])
+ then
+ filePath = clip_table[i][1]
+ mp.commandv("loadfile", clip_table[i][1])
+ clipboard_pasted = true
+ table.remove(clip_table, i)
+ triggered_multipaste[1] = true
+ break
+ end
+ end
+ end
+
+ if filePath ~= nil then
+ for i = 1, #clip_table do
+ if
+ file_exists(clip_table[i][1]) and has_value(o.paste_extensions, clip_table[i][3])
+ or starts_protocol(o.paste_protocols, clip_table[i][1])
+ then
+ mp.commandv("loadfile", clip_table[i][1], "append-play")
+ triggered_multipaste[2] = true
+
+ local temp_filePath = filePath
+ local temp_title_logging = o.file_title_logging
+ filePath = clip_table[i][1]
+ o.file_title_logging = "none"
+ write_log(0, false, o.same_entry_limit, "paste")
+ filePath = temp_filePath
+ o.file_title_logging = temp_title_logging
+ elseif file_exists(clip_table[i][1]) and has_value(o.paste_subtitles, clip_table[i][3]) then
+ mp.commandv("sub-add", clip_table[i][1])
+ file_subtitle_total = file_subtitle_total + 1
+ elseif
+ not has_value(o.paste_extensions, clip_table[i][3])
+ and not has_value(o.paste_subtitles, clip_table[i][3])
+ then
+ msg.warn(
+ "The below was ignored since it is unsupported possibly due to configuration:\n" .. clip_table[i][1]
+ )
+ file_ignored_total = file_ignored_total + 1
+ elseif not file_exists(clip_table[i][1]) then
+ msg.warn("The below doesn't seem to exist:\n" .. clip_table[i][1])
+ file_ignored_total = file_ignored_total + 1
+ else
+ msg.warn("The below was ignored due to an error:\n" .. clip_table[i][1])
+ file_ignored_total = file_ignored_total + 1
+ end
+ end
+ end
+
+ local osd_msg = ""
+ if triggered_multipaste[1] == true then
+ if osd_msg ~= "" then
+ osd_msg = osd_msg .. "\n"
+ end
+ osd_msg = osd_msg .. "Pasted: " .. filePath
+ end
+ if file_subtitle_total > 0 then
+ if osd_msg ~= "" then
+ osd_msg = osd_msg .. "\n"
+ end
+ osd_msg = osd_msg .. "Added " .. file_subtitle_total .. " Subtitle/s"
+ end
+ if triggered_multipaste[2] == true then
+ if osd_msg ~= "" then
+ osd_msg = osd_msg .. "\n"
+ end
+ osd_msg = osd_msg
+ .. "Added Into Playlist "
+ .. #clip_table - file_ignored_total - file_subtitle_total
+ .. " item/s"
+ end
+ if file_ignored_total > 0 then
+ if osd_msg ~= "" then
+ osd_msg = osd_msg .. "\n"
+ end
+ osd_msg = osd_msg .. "Ignored " .. file_ignored_total .. " Item/s"
+ end
+
+ if osd_msg == "" then
+ osd_msg = "Pasted Items Ignored or Unable To Append Into Video:\n" .. clip
+ end
+
+ if o.osd_messages == true then
+ mp.osd_message(osd_msg)
+ end
+ msg.info(osd_msg)
+end
+
+function paste()
+ if o.osd_messages == true then
+ mp.osd_message("Pasting...")
+ end
+ msg.info("Pasting...")
+
+ clip = get_clipboard(clip)
+ if not clip then
+ msg.error("Error: clip is null" .. clip)
+ return
+ end
+ clip, clip_file, clip_time, clip_table = parse_clipboard(clip)
+
+ if #clip_table > 1 then
+ multipaste()
+ else
+ local currentVideoExtension = string.lower(get_extension(clip_file))
+ if filePath == nil then
+ if
+ file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension)
+ or starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("load-file")
+ elseif file_exists(clip_file) and has_value(o.paste_subtitles, currentVideoExtension) then
+ trigger_paste_action("error-subtitle")
+ elseif
+ not has_value(o.paste_extensions, currentVideoExtension)
+ and not has_value(o.paste_subtitles, currentVideoExtension)
+ then
+ trigger_paste_action("log-" .. o.log_paste_idle_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unsupported")
+ end
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("log-" .. o.log_paste_idle_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-missing")
+ end
+ else
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unknown")
+ end
+ end
+ else
+ if file_exists(clip_file) and has_value(o.paste_subtitles, currentVideoExtension) then
+ trigger_paste_action("load-subtitle")
+ elseif o.running_paste_behavior == "playlist" then
+ if
+ filePath ~= clip_file
+ and file_exists(clip_file)
+ and has_value(o.paste_extensions, currentVideoExtension)
+ or filePath ~= clip_file and starts_protocol(o.paste_protocols, clip_file)
+ or filePath == clip_file and file_exists(clip_file) and has_value(
+ o.paste_extensions,
+ currentVideoExtension
+ ) and clip_time == nil
+ or filePath == clip_file and starts_protocol(o.paste_protocols, clip_file) and clip_time == nil
+ then
+ trigger_paste_action("add-playlist")
+ elseif clip_time ~= nil then
+ trigger_paste_action("file-seek")
+ elseif
+ not has_value(o.paste_extensions, currentVideoExtension)
+ and not has_value(o.paste_subtitles, currentVideoExtension)
+ then
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unsupported")
+ end
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-missing")
+ end
+ else
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unknown")
+ end
+ end
+ elseif o.running_paste_behavior == "timestamp" then
+ if clip_time ~= nil then
+ trigger_paste_action("file-seek")
+ elseif
+ file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension)
+ or starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("add-playlist")
+ elseif not has_value(o.paste_extensions, currentVideoExtension) then
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unsupported")
+ end
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-missing")
+ end
+ else
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unknown")
+ end
+ end
+ elseif o.running_paste_behavior == "force" then
+ if
+ filePath ~= clip_file
+ and file_exists(clip_file)
+ and has_value(o.paste_extensions, currentVideoExtension)
+ or filePath ~= clip_file and starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("load-file")
+ elseif clip_time ~= nil then
+ trigger_paste_action("file-seek")
+ elseif
+ file_exists(clip_file) and filePath == clip_file
+ or filePath == clip_file and starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("add-playlist")
+ elseif not has_value(o.paste_extensions, currentVideoExtension) then
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unsupported")
+ end
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-missing")
+ end
+ else
+ trigger_paste_action("log-" .. o.log_paste_running_behavior)
+ if not list_contents or not list_contents[1] then
+ trigger_paste_action("error-unknown")
+ end
+ end
+ end
+ end
+ end
+end
+
+function paste_specific(action)
+ if not action then
+ return
+ end
+
+ if o.osd_messages == true then
+ mp.osd_message("Pasting...")
+ end
+ msg.info("Pasting...")
+
+ clip = get_clipboard(clip)
+ if not clip then
+ msg.error("Error: clip is null" .. clip)
+ return
+ end
+ clip, clip_file, clip_time, clip_table = parse_clipboard(clip)
+
+ if #clip_table > 1 then
+ multipaste()
+ else
+ local currentVideoExtension = string.lower(get_extension(clip_file))
+ if action == "playlist" then
+ if
+ file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension)
+ or starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("add-playlist")
+ elseif
+ not has_value(o.paste_extensions, currentVideoExtension)
+ and not has_value(o.paste_subtitles, currentVideoExtension)
+ then
+ trigger_paste_action("error-unsupported")
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("error-missing")
+ else
+ trigger_paste_action("error-unknown")
+ end
+ end
+
+ if action == "timestamp" then
+ if filePath == nil then
+ trigger_paste_action("error-time")
+ elseif clip_time ~= nil then
+ trigger_paste_action("file-seek")
+ elseif clip_time == nil then
+ trigger_paste_action("error-missingtime")
+ elseif
+ not has_value(o.paste_extensions, currentVideoExtension)
+ and not has_value(o.paste_subtitles, currentVideoExtension)
+ then
+ trigger_paste_action("error-unsupported")
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("error-missing")
+ else
+ trigger_paste_action("error-unknown")
+ end
+ end
+
+ if action == "force" then
+ if
+ filePath ~= clip_file
+ and file_exists(clip_file)
+ and has_value(o.paste_extensions, currentVideoExtension)
+ or filePath ~= clip_file and starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("load-file")
+ elseif
+ file_exists(clip_file) and filePath == clip_file
+ or filePath == clip_file and starts_protocol(o.paste_protocols, clip_file)
+ then
+ trigger_paste_action("error-samefile")
+ elseif
+ not has_value(o.paste_extensions, currentVideoExtension)
+ and not has_value(o.paste_subtitles, currentVideoExtension)
+ then
+ trigger_paste_action("error-unsupported")
+ elseif not file_exists(clip_file) then
+ trigger_paste_action("error-missing")
+ else
+ trigger_paste_action("error-unknown")
+ end
+ end
+ end
+end
+
+mp.register_event("file-loaded", function()
+ list_close_and_trash_collection()
+ filePath, fileTitle, fileLength = get_file()
+ if clipboard_pasted == true then
+ clip = get_clipboard(clip)
+ if not clip then
+ msg.error("Error: clip is null" .. clip)
+ return
+ end
+ clip, clip_file, clip_time, clip_table = parse_clipboard(clip)
+
+ if #clip_table > 1 then
+ for i = 1, #clip_table do
+ if
+ file_exists(clip_table[i][1]) and has_value(o.paste_extensions, clip_table[i][3])
+ or starts_protocol(o.paste_protocols, clip_table[i][1])
+ then
+ clip_file = clip_table[i][1]
+ clip_time = clip_table[i][2]
+ break
+ end
+ end
+ end
+
+ local video_duration = mp.get_property_number("duration")
+ if not clip_time or clip_time > video_duration or clip_time <= 0 then
+ write_log(0, false, o.same_entry_limit, "paste")
+ else
+ write_log(clip_time, false, o.same_entry_limit, "paste")
+ end
+ if filePath == clip_file and clip_time ~= nil then
+ seekTime = clip_time + o.resume_offset
+
+ if seekTime > video_duration then
+ if o.osd_messages == true then
+ mp.osd_message(
+ "Time Paste Exceeds Video Length"
+ .. o.time_seperator
+ .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])
+ )
+ end
+ msg.info("The time pasted exceeds the video length:\n" .. format_time(clip_time))
+ return
+ end
+
+ if seekTime < 0 then
+ seekTime = 0
+ end
+
+ mp.commandv("seek", seekTime, "absolute", "exact")
+ clipboard_pasted = false
+ end
+ end
+
+ if resume_selected == true and seekTime ~= nil then
+ mp.commandv("seek", seekTime, "absolute", "exact")
+ resume_selected = false
+ end
+ mark_chapter()
+end)
+
+mp.observe_property("idle-active", "bool", function(_, v)
+ if v and has_value(available_filters, o.auto_run_list_idle) then
+ display_list(o.auto_run_list_idle, nil, "hide-osd")
+ end
+end)
+
+bind_keys(o.copy_keybind, "copy", copy)
+bind_keys(o.copy_specific_keybind, "copy-specific", function()
+ copy_specific(o.copy_specific_behavior)
+end)
+bind_keys(o.paste_keybind, "paste", paste)
+bind_keys(o.paste_specific_keybind, "paste-specific", function()
+ paste_specific(o.paste_specific_behavior)
+end)
+
+for i = 1, #o.open_list_keybind do
+ if i == 1 then
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list", function()
+ display_list(o.open_list_keybind[i][2])
+ end)
+ else
+ mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list" .. i, function()
+ display_list(o.open_list_keybind[i][2])
+ end)
+ end
+end
diff --git a/ar/.config/mpv/scripts/SmartSkip.lua b/ar/.config/mpv/scripts/SmartSkip.lua
new file mode 100644
index 0000000..05ce108
--- /dev/null
+++ b/ar/.config/mpv/scripts/SmartSkip.lua
@@ -0,0 +1,1936 @@
+-- Copyright (c) 2023, Eisa AlAwadhi
+-- License: BSD 2-Clause License
+-- Creator: Eisa AlAwadhi
+-- Project: SmartSkip
+-- Version: 1.2
+-- Date: 23-09-2023
+
+-- Related forked projects:
+-- https://github.com/detuur/mpv-scripts/blob/master/skiptosilence.lua
+-- https://raw.githubusercontent.com/mpv-player/mpv/master/TOOLS/lua/autoload.lua
+-- https://github.com/mar04/chapters_for_mpv
+-- https://github.com/po5/chapterskip/blob/master/chapterskip.lua
+
+local o = {
+ -----Silence Skip Settings-----
+ silence_audio_level = -40,
+ silence_duration = 0.65,
+ ignore_silence_duration = 5,
+ min_skip_duration = 0,
+ max_skip_duration = 130,
+ keybind_twice_cancel_skip = true,
+ silence_skip_to_end = "playlist-next",
+ add_chapter_on_skip = true,
+ force_mute_on_skip = false,
+ -----Smart Skip Settings-----
+ last_chapter_skip_behavior = [[ [ ["no-chapters", "silence-skip"], ["internal-chapters", "playlist-next"], ["external-chapters", "silence-skip"] ] ]],
+ smart_next_proceed_countdown = true,
+ smart_prev_cancel_countdown = true,
+ -----Chapters Settings-----
+ external_chapters_autoload = true,
+ modified_chapters_autosave = [[ ["no-chapters", "external-chapters"] ]],
+ global_chapters = true,
+ global_chapters_path = "/:dir%mpvconf%/chapters",
+ hash_global_chapters = true,
+ add_chapter_ask_title = false,
+ add_chapter_pause_for_input = false,
+ add_chapter_placeholder_title = "Chapter ",
+ -----Auto-Skip Settings-----
+ autoskip_chapter = true,
+ autoskip_countdown = 3,
+ autoskip_countdown_bulk = false,
+ autoskip_countdown_graceful = false,
+ skip_once = false,
+ categories = [[ [ ["internal-chapters", "prologue>Prologue/^Intro; opening>^OP/ OP$/^Opening; ending>^ED/ ED$/^Ending; preview>Preview$"], ["external-chapters", "idx->0/2"] ] ]],
+ skip = [[ [ ["internal-chapters", "toggle;toggle_idx;opening;ending;preview"], ["external-chapters", "toggle;toggle_idx"] ] ]],
+ -----Autoload Settings-----
+ autoload_playlist = true,
+ autoload_max_entries = 5000,
+ autoload_max_dir_stack = 20,
+ ignore_hidden = true,
+ same_type = false,
+ directory_mode = "auto",
+ images = true,
+ videos = true,
+ audio = true,
+ additional_image_exts = "",
+ additional_video_exts = "",
+ additional_audio_exts = "",
+ -----OSD Messages Settings-----
+ osd_duration = 2500,
+ seek_osd = "osd-msg-bar",
+ chapter_osd = "osd-msg-bar",
+ autoskip_osd = "osd-msg-bar",
+ playlist_osd = true,
+ osd_msg = true,
+ -----Keybind Settings-----
+ toggle_autoload_keybind = [[ [""] ]],
+ toggle_autoskip_keybind = [[ ["ctrl+."] ]],
+ toggle_category_autoskip_keybind = [[ ["alt+."] ]],
+ cancel_autoskip_countdown_keybind = [[ ["esc", "n"] ]],
+ proceed_autoskip_countdown_keybind = [[ ["enter", "y"] ]],
+ add_chapter_keybind = [[ ["n"] ]],
+ remove_chapter_keybind = [[ ["alt+n"] ]],
+ edit_chapter_keybind = [[ [""] ]],
+ write_chapters_keybind = [[ ["ctrl+n"] ]],
+ bake_chapters_keybind = [[ [""] ]],
+ chapter_next_keybind = [[ ["ctrl+right"] ]],
+ chapter_prev_keybind = [[ ["ctrl+left"] ]],
+ smart_next_keybind = [[ [">"] ]],
+ smart_prev_keybind = [[ ["<"] ]],
+ silence_skip_keybind = [[ ["?"] ]],
+}
+
+local mp = require("mp")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+local options = require("mp.options")
+options.read_options(o, nil, function(list)
+ split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
+ if
+ list.videos
+ or list.additional_video_exts
+ or list.audio
+ or list.additional_audio_exts
+ or list.images
+ or list.additional_image_exts
+ then
+ create_extensions()
+ end
+ if list.directory_mode then
+ validate_directory_mode()
+ end
+end)
+
+if o.add_chapter_on_skip ~= false and o.add_chapter_on_skip ~= true then
+ o.add_chapter_on_skip = utils.parse_json(o.add_chapter_on_skip)
+end
+if o.modified_chapters_autosave ~= false and o.modified_chapters_autosave ~= true then
+ o.modified_chapters_autosave = utils.parse_json(o.modified_chapters_autosave)
+end
+o.last_chapter_skip_behavior = utils.parse_json(o.last_chapter_skip_behavior)
+if utils.parse_json(o.skip) ~= nil then
+ o.skip = utils.parse_json(o.skip)
+end
+if utils.parse_json(o.categories) ~= nil then
+ o.categories = utils.parse_json(o.categories)
+end
+if o.skip_once ~= false and o.skip_once ~= true then
+ o.skip_once = utils.parse_json(o.skip_once)
+end
+
+if o.global_chapters_path:match("/:dir%%mpvconf%%") then --1.2# add variables for specifying path via user-config
+ o.global_chapters_path = o.global_chapters_path:gsub("/:dir%%mpvconf%%", mp.find_config_file("."))
+elseif o.global_chapters_path:match("/:dir%%script%%") then
+ o.global_chapters_path = o.global_chapters_path:gsub("/:dir%%script%%", debug.getinfo(1).source:match("@?(.*/)"))
+elseif o.global_chapters_path:match("/:var%%(.*)%%") then
+ local os_variable = o.global_chapters_path:match("/:var%%(.*)%%")
+ o.global_chapters_path = o.global_chapters_path:gsub("/:var%%(.*)%%", os.getenv(os_variable))
+end
+
+o.toggle_autoload_keybind = utils.parse_json(o.toggle_autoload_keybind)
+o.toggle_autoskip_keybind = utils.parse_json(o.toggle_autoskip_keybind)
+o.cancel_autoskip_countdown_keybind = utils.parse_json(o.cancel_autoskip_countdown_keybind)
+o.proceed_autoskip_countdown_keybind = utils.parse_json(o.proceed_autoskip_countdown_keybind)
+o.toggle_category_autoskip_keybind = utils.parse_json(o.toggle_category_autoskip_keybind)
+o.add_chapter_keybind = utils.parse_json(o.add_chapter_keybind)
+o.remove_chapter_keybind = utils.parse_json(o.remove_chapter_keybind)
+o.write_chapters_keybind = utils.parse_json(o.write_chapters_keybind)
+o.edit_chapter_keybind = utils.parse_json(o.edit_chapter_keybind)
+o.bake_chapters_keybind = utils.parse_json(o.bake_chapters_keybind)
+o.chapter_prev_keybind = utils.parse_json(o.chapter_prev_keybind)
+o.chapter_next_keybind = utils.parse_json(o.chapter_next_keybind)
+o.smart_prev_keybind = utils.parse_json(o.smart_prev_keybind)
+o.smart_next_keybind = utils.parse_json(o.smart_next_keybind)
+o.silence_skip_keybind = utils.parse_json(o.silence_skip_keybind)
+
+package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
+local user_input_module, input = pcall(require, "user-input-module")
+
+if o.osd_duration == -1 then
+ o.osd_duration = (mp.get_property_number("osd-duration") or 1000)
+end
+local speed_state = 1
+local pause_state = false
+local mute_state = false
+local sub_state = nil
+local secondary_sub_state = nil
+local vid_state = nil
+local skip_flag = false
+local window_state = nil
+local force_silence_skip = false
+local initial_skip_time = 0
+local initial_chapter_count = 0
+local chapter_state = "no-chapters"
+local file_length = 0
+local keep_open_state = "yes"
+if mp.get_property("config") ~= "no" then
+ keep_open_state = mp.get_property("keep-open")
+end
+local osd_duration_default = (mp.get_property_number("osd-duration") or 1000)
+local autoload_playlist = o.autoload_playlist
+local autoskip_chapter = o.autoskip_chapter
+local playlist_osd = false
+local autoskip_playlist_osd = false
+local g_playlist_pos = 0
+local g_opt_categories = o.categories
+local g_opt_skip_once = false
+o.autoskip_countdown = math.floor(o.autoskip_countdown)
+local g_autoskip_countdown = o.autoskip_countdown
+local g_autoskip_countdown_flag = false
+local categories = {
+ toggle = "",
+ toggle_idx = "",
+}
+local autoskip_osd = o.autoskip_osd
+if o.autoskip_osd == "osd-msg-bar" then
+ autoskip_osd = "osd-bar"
+end
+if o.autoskip_osd == "osd-msg" then
+ autoskip_osd = "no-osd"
+end
+
+-- utility functions --
+function has_value(tab, val, array2d)
+ if not tab then
+ return msg.error("check value passed")
+ end
+ if not val then
+ return msg.error("check value passed")
+ end
+ if not array2d then
+ for index, value in ipairs(tab) do
+ if string.lower(value) == string.lower(val) then
+ return true
+ end
+ end
+ end
+ if array2d then
+ for i = 1, #tab do
+ if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then
+ return true
+ end
+ end
+ end
+
+ return false
+end
+
+function esc_string(str)
+ return str:gsub("([%p])", "%%%1")
+end
+
+function prompt_msg(text, duration)
+ if not text then
+ return
+ end
+ if not duration then
+ duration = o.osd_duration
+ end
+ if o.osd_msg then
+ mp.commandv("show-text", text, duration)
+ end
+ msg.info(text)
+end
+
+function bind_keys(keys, name, func, opts)
+ if not keys then
+ mp.add_forced_key_binding(keys, name, func, opts)
+ return
+ end
+
+ for i = 1, #keys do
+ if i == 1 then
+ mp.add_forced_key_binding(keys[i], name, func, opts)
+ else
+ mp.add_forced_key_binding(keys[i], name .. i, func, opts)
+ end
+ end
+end
+
+function unbind_keys(keys, name)
+ if not keys then
+ mp.remove_key_binding(name)
+ return
+ end
+
+ for i = 1, #keys do
+ if i == 1 then
+ mp.remove_key_binding(name)
+ else
+ mp.remove_key_binding(name .. i)
+ end
+ end
+end
+
+-- skip-silence utility functions --
+function restoreProp(timepos, pause)
+ if not timepos then
+ timepos = mp.get_property_number("time-pos")
+ end
+ if not pause then
+ pause = pause_state
+ end
+
+ mp.set_property("vid", vid_state)
+ mp.set_property("force-window", window_state)
+ mp.set_property_bool("mute", mute_state)
+ mp.set_property("speed", speed_state)
+ mp.unobserve_property(foundSilence)
+ mp.command("no-osd af remove @skiptosilence")
+ mp.set_property_bool("pause", pause)
+ mp.set_property_number("time-pos", timepos)
+ mp.set_property("sub-visibility", sub_state)
+ mp.set_property("secondary-sub-visibility", secondary_sub_state)
+ timer:kill()
+ skip_flag = false
+end
+
+function handleMinMaxDuration(timepos)
+ if not skip_flag then
+ return
+ end
+ if not timepos then
+ timepos = mp.get_property_number("time-pos")
+ end
+
+ skip_duration = timepos - initial_skip_time
+ if o.min_skip_duration > 0 and skip_duration <= o.min_skip_duration then
+ restoreProp(initial_skip_time)
+ prompt_msg("Skipping Cancelled\nSilence less than minimum")
+ return true
+ end
+ if o.max_skip_duration > 0 and skip_duration >= o.max_skip_duration then
+ restoreProp(initial_skip_time)
+ prompt_msg("Skipping Cancelled\nSilence is more than configured maximum")
+ return true
+ end
+ return false
+end
+
+function setKeepOpenState()
+ if o.silence_skip_to_end == "playlist-next" then
+ mp.set_property("keep-open", "yes")
+ else
+ mp.set_property("keep-open", "always")
+ end
+end
+
+function eofHandler(name, val)
+ if val and skip_flag then
+ if o.silence_skip_to_end == "playlist-next" then
+ restoreProp((mp.get_property_native("duration") or 0))
+ if mp.get_property_native("playlist-playing-pos") + 1 == mp.get_property_native("playlist-count") then
+ prompt_msg("Skipped to end at " .. mp.get_property_osd("duration"))
+ else
+ mp.commandv("playlist-next")
+ end
+ elseif o.silence_skip_to_end == "cancel" then
+ prompt_msg("Skipping Cancelled\nSilence not detected")
+ restoreProp(initial_skip_time)
+ elseif o.silence_skip_to_end == "pause" then
+ prompt_msg("Skipped to end at " .. mp.get_property_osd("duration"))
+ restoreProp((mp.get_property_native("duration") or 0), true)
+ end
+ end
+end
+
+-- smart-skip main code --
+function smartNext()
+ if g_autoskip_countdown_flag and o.smart_next_proceed_countdown then
+ proceed_autoskip(true)
+ return
+ end
+ local next_action = "silence-skip"
+ local chapters_count = (mp.get_property_number("chapters") or 0)
+ local chapter = (mp.get_property_number("chapter") or 0)
+ local current_playlist = (mp.get_property_native("playlist-playing-pos") + 1 or 0)
+ local total_playlist = (mp.get_property_native("playlist-count") or 0)
+
+ if chapter + 2 <= chapters_count then
+ next_action = "chapter-next"
+ elseif
+ chapter + 2 > chapters_count and (initial_chapter_count == 0 or chapters_count == 0 or force_silence_skip)
+ then
+ if chapters_count == 0 then
+ force_silence_skip = true
+ end
+ next_action = "silence-skip"
+ elseif chapter + 1 >= chapters_count then
+ for i = 1, #o.last_chapter_skip_behavior do
+ if o.last_chapter_skip_behavior[i] and o.last_chapter_skip_behavior[i][1] == chapter_state then
+ next_action = o.last_chapter_skip_behavior[i][2]
+ break
+ end
+ end
+ end
+
+ if next_action == "playlist-next" and current_playlist == total_playlist then
+ next_action = "chapter-next"
+ end
+
+ if next_action == "silence-skip" then
+ silenceSkip()
+ end
+ if next_action == "chapter-next" then
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(o.chapter_osd, "add", "chapter", 1)
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ end
+ if next_action == "playlist-next" then
+ mp.command("playlist_next")
+ end
+end
+
+function smartPrev()
+ if skip_flag then
+ restoreProp(initial_skip_time)
+ return
+ end
+ if g_autoskip_countdown_flag and o.smart_prev_cancel_countdown then
+ kill_chapterskip_countdown("osd")
+ return
+ end
+ local chapters_count = (mp.get_property_number("chapters") or 0)
+ local chapter = (mp.get_property_number("chapter") or 0)
+ local timepos = (mp.get_property_native("time-pos") or 0)
+
+ if chapter - 1 < 0 and timepos > 1 and chapters_count == 0 then
+ mp.commandv("seek", 0, "absolute", "exact")
+
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(o.seek_osd, "show-progress")
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ elseif chapter - 1 < 0 and timepos < 1 then
+ mp.command("playlist_prev")
+ elseif chapter - 1 <= chapters_count then
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(o.chapter_osd, "add", "chapter", -1)
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ end
+end
+
+-- chapter-next/prev main code --
+function chapterSeek(direction)
+ if skip_flag and direction == -1 then
+ restoreProp(initial_skip_time)
+ return
+ end
+
+ local chapters_count = (mp.get_property_number("chapters") or 0)
+ local chapter = (mp.get_property_number("chapter") or 0)
+ local timepos = (mp.get_property_native("time-pos") or 0)
+
+ if chapter + direction < 0 and timepos > 1 and chapters_count == 0 then
+ mp.commandv("seek", 0, "absolute", "exact")
+
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(o.seek_osd, "show-progress")
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ elseif chapter + direction < 0 and timepos < 1 then
+ mp.command("playlist_prev")
+ elseif chapter + direction >= chapters_count then
+ mp.command("playlist_next")
+ else
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(o.chapter_osd, "add", "chapter", direction)
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ end
+end
+
+-- silence skip main code --
+function silenceSkip(action)
+ if skip_flag then
+ if o.keybind_twice_cancel_skip then
+ restoreProp(initial_skip_time)
+ end
+ return
+ end
+ initial_skip_time = (mp.get_property_native("time-pos") or 0)
+ if math.floor(initial_skip_time) == math.floor(mp.get_property_native("duration") or 0) then
+ return
+ end
+ local width = mp.get_property_native("osd-width")
+ local height = mp.get_property_native("osd-height")
+ mp.set_property_native("geometry", ("%dx%d"):format(width, height))
+ mp.commandv(o.seek_osd, "show-progress")
+
+ mp.command(
+ "no-osd af add @skiptosilence:lavfi=[silencedetect=noise="
+ .. o.silence_audio_level
+ .. "dB:d="
+ .. o.silence_duration
+ .. "]"
+ )
+
+ mp.observe_property("af-metadata/skiptosilence", "string", foundSilence)
+
+ sub_state = mp.get_property("sub-visibility")
+ mp.set_property("sub-visibility", "no")
+ secondary_sub_state = mp.get_property("secondary-sub-visibility")
+ mp.set_property("secondary-sub-visibility", "no")
+ window_state = mp.get_property("force-window")
+ mp.set_property("force-window", "yes")
+ vid_state = mp.get_property("vid")
+ mp.set_property("vid", "no")
+ mute_state = mp.get_property_native("mute")
+ if o.force_mute_on_skip then
+ mp.set_property_bool("mute", true)
+ end
+ pause_state = mp.get_property_native("pause")
+ mp.set_property_bool("pause", false)
+ speed_state = mp.get_property_native("speed")
+ mp.set_property("speed", 100)
+ setKeepOpenState()
+ skip_flag = true
+
+ timer = mp.add_periodic_timer(0.5, function()
+ local video_time = (mp.get_property_native("time-pos") or 0)
+ handleMinMaxDuration(video_time)
+ if skip_flag then
+ mp.commandv(o.seek_osd, "show-progress")
+ end
+ end)
+end
+
+function foundSilence(name, value)
+ if value == "{}" or value == nil then
+ return
+ end
+
+ timecode = tonumber(string.match(value, "%d+%.?%d+"))
+ if timecode == nil or timecode < initial_skip_time + o.ignore_silence_duration then
+ return
+ end
+
+ if handleMinMaxDuration(timecode) then
+ return
+ end
+
+ restoreProp(timecode)
+
+ mp.add_timeout(0.05, function()
+ prompt_msg("Skipped to silence 🕒 " .. mp.get_property_osd("time-pos"))
+ end)
+ if o.add_chapter_on_skip == true or has_value(o.add_chapter_on_skip, chapter_state) then
+ mp.add_timeout(0.05, add_chapter)
+ end
+ skip_flag = false
+end
+
+-- modified fork of chapters_for_mpv --
+--[[
+Copyright (c) 2023 Mariusz Libera <mariusz.libera@gmail.com>
+
+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.
+--]]
+
+-- to debug run mpv with arg: --msg-level=SmartSkip=debug
+-- to test o.run mpv with arg: --script-opts=SmartSkip-OPTION=VALUE
+
+local chapters_modified = false
+
+msg.debug("options:", utils.to_string(options))
+
+-- CHAPTER MANIPULATION --------------------------------------------------------
+
+function change_title_callback(user_input, err, chapter_index)
+ if user_input == nil or err ~= nil then
+ msg.warn("no chapter title provided:", err)
+ return
+ end
+
+ local chapter_list = mp.get_property_native("chapter-list")
+
+ if chapter_index > mp.get_property_number("chapter-list/count") then
+ msg.warn("can't set chapter title")
+ return
+ end
+
+ chapter_list[chapter_index].title = user_input
+
+ mp.set_property_native("chapter-list", chapter_list)
+ chapters_modified = true
+end
+
+function edit_chapter()
+ local mpv_chapter_index = mp.get_property_number("chapter")
+ local chapter_list = mp.get_property_native("chapter-list")
+
+ if mpv_chapter_index == nil or mpv_chapter_index == -1 then
+ msg.verbose("no chapter selected, nothing to edit")
+ return
+ end
+
+ if not user_input_module then
+ msg.error("no mpv-user-input, can't get user input, install: https://github.com/CogentRedTester/mpv-user-input")
+ return
+ end
+ input.get_user_input(change_title_callback, {
+ request_text = "title of the chapter:",
+ default_input = chapter_list[mpv_chapter_index + 1].title,
+ cursor_pos = #chapter_list[mpv_chapter_index + 1].title + 1,
+ }, mpv_chapter_index + 1)
+
+ if o.add_chapter_pause_for_input then
+ mp.set_property_bool("pause", true)
+ mp.osd_message(" ", 0.1)
+ end
+end
+
+function add_chapter(timepos)
+ if not timepos then
+ timepos = mp.get_property_number("time-pos")
+ end
+ local chapter_list = mp.get_property_native("chapter-list")
+
+ if #chapter_list > 0 then
+ for i = 1, #chapter_list do
+ if math.floor(chapter_list[i].time) == math.floor(timepos) then
+ msg.debug("failed to add chapter, chapter exists in same position")
+ return
+ end
+ end
+ end
+
+ local chapter_index = (mp.get_property_number("chapter") or -1) + 2
+
+ table.insert(chapter_list, chapter_index, { title = "", time = timepos })
+
+ msg.debug("inserting new chapter at ", chapter_index, " chapter_", " time: ", timepos)
+
+ mp.set_property_native("chapter-list", chapter_list)
+ chapters_modified = true
+
+ if o.add_chapter_ask_title then
+ if not user_input_module then
+ msg.error(
+ "no mpv-user-input, can't get user input, install: https://github.com/CogentRedTester/mpv-user-input"
+ )
+ return
+ end
+ -- ask user for chapter title
+ input.get_user_input(change_title_callback, {
+ request_text = "title of the chapter:",
+ default_input = o.placeholder_title .. chapter_index,
+ cursor_pos = #(o.placeholder_title .. chapter_index) + 1,
+ }, chapter_index)
+
+ if o.add_chapter_pause_for_input then
+ mp.set_property_bool("pause", true)
+ -- FIXME: for whatever reason osd gets hidden when we pause the
+ -- playback like that, workaround to make input prompt appear
+ -- right away without requiring mouse or keyboard action
+ mp.osd_message(" ", 0.1)
+ end
+ end
+end
+
+function remove_chapter()
+ local chapter_count = mp.get_property_number("chapter-list/count")
+
+ if chapter_count < 1 then
+ msg.verbose("no chapters to remove")
+ return
+ end
+
+ local chapter_list = mp.get_property_native("chapter-list")
+ local current_chapter = mp.get_property_number("chapter") + 1
+
+ table.remove(chapter_list, current_chapter)
+ msg.debug("removing chapter", current_chapter)
+
+ mp.set_property_native("chapter-list", chapter_list)
+ chapters_modified = true
+end
+
+-- UTILITY FUNCTIONS -----------------------------------------------------------
+
+function detect_os()
+ if package.config:sub(1, 1) == "/" then
+ return "unix"
+ else
+ return "windows"
+ end
+end
+
+-- for unix use only
+-- returns a table of command path and varargs, or nil if command was not found
+function command_exists(command, ...)
+ msg.debug("looking for command:", command)
+ -- msg.debug("args:", )
+ local process = mp.command_native({
+ name = "subprocess",
+ capture_stdout = true,
+ capture_stderr = true,
+ playback_only = false,
+ args = { "sh", "-c", "command -v -- " .. command },
+ })
+
+ if process.status == 0 then
+ local command_path = process.stdout:gsub("\n", "")
+ msg.debug("command found:", command_path)
+ return { command_path, ... }
+ else
+ msg.debug("command not found:", command)
+ return nil
+ end
+end
+
+function mkdir(path)
+ local args = nil
+
+ if detect_os() == "unix" then
+ args = { "mkdir", "-p", "--", path }
+ else
+ args = { "powershell", "-NoProfile", "-Command", "mkdir", path }
+ end
+
+ local process = mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ args = args,
+ })
+
+ if process.status == 0 then
+ msg.debug("mkdir success:", path)
+ return true
+ else
+ msg.error("mkdir failure:", path)
+ return false
+ end
+end
+
+-- returns md5 hash of the full path of the current media file
+function hash()
+ local path = mp.get_property("path")
+ if path == nil then
+ msg.debug("something is wrong with the path, can't get full_path, can't hash it")
+ return
+ end
+
+ msg.debug("hashing:", path)
+
+ local cmd = {
+ name = "subprocess",
+ capture_stdout = true,
+ playback_only = false,
+ }
+ local args = nil
+
+ if detect_os() == "unix" then
+ local md5 = command_exists("md5sum")
+ or command_exists("md5")
+ or command_exists("openssl", "md5 | cut -d ' ' -f 2")
+ if md5 == nil then
+ msg.warn("no md5 command found, can't generate hash")
+ return
+ end
+ md5 = table.concat(md5, " ")
+ cmd["stdin_data"] = path
+ args = { "sh", "-c", md5 .. " | cut -d ' ' -f 1 | tr '[:lower:]' '[:upper:]'" }
+ else --windows
+ -- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.3
+ local hash_command = '$s = [System.IO.MemoryStream]::new(); $w = [System.IO.StreamWriter]::new($s); $w.write("'
+ .. path
+ .. '"); $w.Flush(); $s.Position = 0; Get-FileHash -Algorithm MD5 -InputStream $s | Select-Object -ExpandProperty Hash'
+ args = { "powershell", "-NoProfile", "-Command", hash_command }
+ end
+ cmd["args"] = args
+ msg.debug("hash cmd:", utils.to_string(cmd))
+ local process = mp.command_native(cmd)
+
+ if process.status == 0 then
+ local hash = process.stdout:gsub("%s+", "")
+ msg.debug("hash:", hash)
+ return hash
+ else
+ msg.warn("hash function failed")
+ return
+ end
+end
+
+function construct_ffmetadata()
+ local path = mp.get_property("path")
+ if path == nil then
+ msg.debug("something is wrong with the path, can't get full_path")
+ return
+ end
+
+ local chapter_count = mp.get_property_number("chapter-list/count")
+ local all_chapters = mp.get_property_native("chapter-list")
+
+ local ffmetadata = ";FFMETADATA1\n;file=" .. path
+
+ for i, c in ipairs(all_chapters) do
+ local c_title = c.title
+ local c_start = c.time * 1000000000
+ local c_end
+
+ if i < chapter_count then
+ c_end = all_chapters[i + 1].time * 1000000000
+ else
+ c_end = (mp.get_property_number("duration") or c.time) * 1000000000
+ end
+
+ msg.debug(i, "c_title", c_title, "c_start:", c_start, "c_end", c_end)
+
+ ffmetadata = ffmetadata .. "\n[CHAPTER]\nSTART=" .. c_start .. "\nEND=" .. c_end .. "\ntitle=" .. c_title
+ end
+
+ return ffmetadata
+end
+
+-- FILE IO ---------------------------------------------------------------------
+
+-- args:
+-- osd - if true, display an osd message
+-- force -- if true write chapters file even if there are no changes
+-- on success returns path of the chapters file, nil on failure
+function write_chapters(...)
+ local chapters_count = (mp.get_property_number("chapters") or 0)
+ local osd, force = ...
+ if not force and not chapters_modified then
+ msg.debug("nothing to write")
+ return
+ end
+ if initial_chapter_count == 0 and chapters_count == 0 then
+ return
+ end
+
+ -- figure out the directory
+ local chapters_dir
+ if o.global_chapters then
+ local dir = utils.file_info(o.global_chapters_path)
+ if dir then
+ if dir.is_dir then
+ msg.debug("o.global_chapters_path exists:", o.global_chapters_path)
+ chapters_dir = o.global_chapters_path
+ else
+ msg.error("o.global_chapters_path is not a directory")
+ return
+ end
+ else
+ msg.verbose("o.global_chapters_path doesn't exists:", o.global_chapters_path)
+ if mkdir(o.global_chapters_path) then
+ chapters_dir = o.global_chapters_path
+ else
+ return
+ end
+ end
+ else
+ chapters_dir = utils.split_path(mp.get_property("path"))
+ end
+
+ -- and the name
+ local name = mp.get_property("filename")
+ if o.hash_global_chapters and o.global_chapters then
+ name = hash()
+ if name == nil then
+ msg.warn("hash function failed, fallback to filename")
+ name = mp.get_property("filename")
+ end
+ end
+
+ local chapters_file_path = utils.join_path(chapters_dir, name .. ".ffmetadata")
+ --1.09#HERE I SHOULD ADD SOME SORT OF DELETE FUNCTION IN CASE CHAPTER COUNT IS 0
+ msg.debug("opening for writing:", chapters_file_path)
+ local chapters_file = io.open(chapters_file_path, "w")
+ if chapters_file == nil then
+ msg.error("could not open chapter file for writing")
+ return
+ end
+
+ local success, error = chapters_file:write(construct_ffmetadata())
+ chapters_file:close()
+
+ if success then
+ if osd then
+ prompt_msg("Chapters written to:" .. chapters_file_path)
+ end
+ return chapters_file_path
+ else
+ msg.error("error writing chapters file:", error)
+ return
+ end
+end
+
+function load_chapters()
+ local path = mp.get_property("path")
+ local expected_chapters_file = utils.join_path(utils.split_path(path), mp.get_property("filename") .. ".ffmetadata")
+
+ msg.debug("looking for:", expected_chapters_file)
+
+ local file = utils.file_info(expected_chapters_file)
+
+ if file then
+ msg.debug("found in the local directory, loading..")
+ mp.set_property("file-local-options/chapters-file", expected_chapters_file)
+ chapter_state = "external-chapters"
+ return
+ end
+
+ if not o.global_chapters then
+ msg.debug("not in local, global chapters not enabled, aborting search")
+ return
+ end
+
+ msg.debug("looking in the global directory")
+
+ if o.hash_global_chapters then
+ local hashed_path = hash()
+ if hashed_path then
+ expected_chapters_file = utils.join_path(o.global_chapters_path, hashed_path .. ".ffmetadata")
+ else
+ msg.debug("hash function failed, fallback to path")
+ expected_chapters_file =
+ utils.join_path(o.global_chapters_path, mp.get_property("filename") .. ".ffmetadata")
+ end
+ else
+ expected_chapters_file = utils.join_path(o.global_chapters_path, mp.get_property("filename") .. ".ffmetadata")
+ end
+
+ msg.debug("looking for:", expected_chapters_file)
+
+ file = utils.file_info(expected_chapters_file)
+
+ if file then
+ msg.debug("found in the global directory, loading..")
+ mp.set_property("file-local-options/chapters-file", expected_chapters_file)
+ chapter_state = "external-chapters"
+ return
+ end
+
+ msg.debug("chapters file not found")
+end
+
+function bake_chapters()
+ if mp.get_property_number("chapter-list/count") == 0 then
+ msg.verbose("no chapters present")
+ return
+ end
+
+ local chapters_file_path = write_chapters(false, true)
+ if not chapters_file_path then
+ msg.error("no chapters file")
+ return
+ end
+
+ local filename = mp.get_property("filename")
+ local output_name
+
+ -- extract file extension
+ local reverse_dot_index = filename:reverse():find(".", 1, true)
+ if reverse_dot_index == nil then
+ msg.warning("file has no extension, fallback to .mkv")
+ output_name = filename .. ".chapters.mkv"
+ else
+ local dot_index = #filename + 1 - reverse_dot_index
+ local ext = filename:sub(dot_index + 1)
+ msg.debug("ext:", ext)
+ if ext ~= "mkv" and ext ~= "mp4" and ext ~= "webm" then
+ msg.debug("fallback to .mkv")
+ ext = "mkv"
+ end
+ output_name = filename:sub(1, dot_index) .. "chapters." .. ext
+ end
+
+ local file_path = mp.get_property("path")
+ local output_path = utils.join_path(utils.split_path(file_path), output_name)
+
+ local args = {
+ "ffmpeg",
+ "-y",
+ "-i",
+ file_path,
+ "-i",
+ chapters_file_path,
+ "-map_metadata",
+ "1",
+ "-codec",
+ "copy",
+ output_path,
+ }
+
+ msg.debug("args:", utils.to_string(args))
+
+ local process = mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ args = args,
+ })
+
+ if process.status == 0 then
+ prompt_msg("file written to " .. output_path)
+ else
+ msg.error("failed to write file:\n", process.stderr)
+ end
+end
+
+--modified fork of autoload script--
+function toggle_autoload()
+ if autoload_playlist == true then
+ prompt_msg("○ Auto-Load Disabled")
+ autoload_playlist = false
+ elseif autoload_playlist == false then
+ prompt_msg("● Auto-Load Enabled")
+ autoload_playlist = true
+ end
+ if autoload_playlist then
+ find_and_add_entries()
+ end
+end
+
+function Set(t)
+ local set = {}
+ for _, v in pairs(t) do
+ set[v] = true
+ end
+ return set
+end
+
+function SetUnion(a, b)
+ for k in pairs(b) do
+ a[k] = true
+ end
+ return a
+end
+
+function Split(s)
+ local set = {}
+ for v in string.gmatch(s, "([^,]+)") do
+ set[v] = true
+ end
+ return set
+end
+
+EXTENSIONS_VIDEO = Set({
+ "3g2",
+ "3gp",
+ "avi",
+ "flv",
+ "m2ts",
+ "m4v",
+ "mj2",
+ "mkv",
+ "mov",
+ "mp4",
+ "mpeg",
+ "mpg",
+ "ogv",
+ "rmvb",
+ "webm",
+ "wmv",
+ "y4m",
+})
+
+EXTENSIONS_AUDIO = Set({
+ "aiff",
+ "ape",
+ "au",
+ "flac",
+ "m4a",
+ "mka",
+ "mp3",
+ "oga",
+ "ogg",
+ "ogm",
+ "opus",
+ "wav",
+ "wma",
+})
+
+EXTENSIONS_IMAGES = Set({
+ "avif",
+ "bmp",
+ "gif",
+ "j2k",
+ "jp2",
+ "jpeg",
+ "jpg",
+ "jxl",
+ "png",
+ "svg",
+ "tga",
+ "tif",
+ "tiff",
+ "webp",
+})
+
+function split_option_exts(video, audio, image)
+ if video then
+ o.additional_video_exts = Split(o.additional_video_exts)
+ end
+ if audio then
+ o.additional_audio_exts = Split(o.additional_audio_exts)
+ end
+ if image then
+ o.additional_image_exts = Split(o.additional_image_exts)
+ end
+end
+split_option_exts(true, true, true)
+
+function create_extensions()
+ EXTENSIONS = {}
+ if o.videos then
+ SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_VIDEO), o.additional_video_exts)
+ end
+ if o.audio then
+ SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_AUDIO), o.additional_audio_exts)
+ end
+ if o.images then
+ SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_IMAGES), o.additional_image_exts)
+ end
+end
+create_extensions()
+
+function validate_directory_mode()
+ if o.directory_mode ~= "recursive" and o.directory_mode ~= "lazy" and o.directory_mode ~= "ignore" then
+ o.directory_mode = nil
+ end
+end
+validate_directory_mode()
+
+function add_files(files)
+ local oldcount = mp.get_property_number("playlist-count", 1)
+ for i = 1, #files do
+ mp.commandv("loadfile", files[i][1], "append")
+ mp.commandv("playlist-move", oldcount + i - 1, files[i][2])
+ end
+end
+
+function get_extension(path)
+ match = string.match(path, "%.([^%.]+)$")
+ if match == nil then
+ return "nomatch"
+ else
+ return match
+ end
+end
+
+table.filter = function(t, iter)
+ for i = #t, 1, -1 do
+ if not iter(t[i]) then
+ table.remove(t, i)
+ end
+ end
+end
+
+table.append = function(t1, t2)
+ local t1_size = #t1
+ for i = 1, #t2 do
+ t1[t1_size + i] = t2[i]
+ end
+end
+
+-- alphanum sorting for humans in Lua
+-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
+
+function alphanumsort(filenames)
+ local function padnum(n, d)
+ return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d)) or ("%03d%s"):format(#n, n)
+ end
+
+ local tuples = {}
+ for i, f in ipairs(filenames) do
+ tuples[i] = { f:lower():gsub("0*(%d+)%.?(%d*)", padnum), f }
+ end
+ table.sort(tuples, function(a, b)
+ return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1]
+ end)
+ for i, tuple in ipairs(tuples) do
+ filenames[i] = tuple[2]
+ end
+ return filenames
+end
+
+local autoloaded = nil
+local added_entries = {}
+local autoloaded_dir = nil
+
+function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_files, extensions)
+ if dir_depth == o.autoload_max_dir_stack then
+ return
+ end
+ msg.trace("scanning: " .. path)
+ local files = utils.readdir(path, "files") or {}
+ local dirs = dir_mode ~= "ignore" and utils.readdir(path, "dirs") or {}
+ local prefix = path == "." and "" or path
+ table.filter(files, function(v)
+ -- The current file could be a hidden file, ignoring it doesn't load other
+ -- files from the current directory.
+ if o.ignore_hidden and not (prefix .. v == current_file) and string.match(v, "^%.") then
+ return false
+ end
+ local ext = get_extension(v)
+ if ext == nil then
+ return false
+ end
+ return extensions[string.lower(ext)]
+ end)
+ table.filter(dirs, function(d)
+ return not (o.ignore_hidden and string.match(d, "^%."))
+ end)
+ alphanumsort(files)
+ alphanumsort(dirs)
+
+ for i, file in ipairs(files) do
+ files[i] = prefix .. file
+ end
+
+ table.append(total_files, files)
+ if dir_mode == "recursive" then
+ for _, dir in ipairs(dirs) do
+ scan_dir(
+ prefix .. dir .. separator,
+ current_file,
+ dir_mode,
+ separator,
+ dir_depth + 1,
+ total_files,
+ extensions
+ )
+ end
+ else
+ for i, dir in ipairs(dirs) do
+ dirs[i] = prefix .. dir
+ end
+ table.append(total_files, dirs)
+ end
+end
+
+function find_and_add_entries()
+ local path = mp.get_property("path", "")
+ local dir, filename = utils.split_path(path)
+ msg.trace(("dir: %s, filename: %s"):format(dir, filename))
+ if not autoload_playlist then
+ msg.verbose("stopping: autoload_playlist is disabled")
+ return
+ elseif #dir == 0 then
+ msg.verbose("stopping: not a local path")
+ return
+ end
+
+ local pl_count = mp.get_property_number("playlist-count", 1)
+ this_ext = get_extension(filename)
+ -- check if this is a manually made playlist
+ if (pl_count > 1 and autoloaded == nil) or (pl_count == 1 and EXTENSIONS[string.lower(this_ext)] == nil) then
+ msg.verbose("stopping: manually made playlist")
+ return
+ else
+ if pl_count == 1 then
+ autoloaded = true
+ autoloaded_dir = dir
+ added_entries = {}
+ end
+ end
+
+ local extensions = {}
+ if o.same_type then
+ if EXTENSIONS_VIDEO[string.lower(this_ext)] ~= nil then
+ extensions = EXTENSIONS_VIDEO
+ elseif EXTENSIONS_AUDIO[string.lower(this_ext)] ~= nil then
+ extensions = EXTENSIONS_AUDIO
+ else
+ extensions = EXTENSIONS_IMAGES
+ end
+ else
+ extensions = EXTENSIONS
+ end
+
+ local pl = mp.get_property_native("playlist", {})
+ local pl_current = mp.get_property_number("playlist-pos-1", 1)
+ msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current, utils.to_string(pl)))
+
+ local files = {}
+ do
+ local dir_mode = o.directory_mode or mp.get_property("directory-mode", "lazy")
+ local separator = mp.get_property_native("platform") == "windows" and "\\" or "/"
+ scan_dir(autoloaded_dir, path, dir_mode, separator, 0, files, extensions)
+ end
+
+ if next(files) == nil then
+ msg.verbose("no other files or directories in directory")
+ return
+ end
+
+ -- Find the current pl entry (dir+"/"+filename) in the sorted dir list
+ local current
+ for i = 1, #files do
+ if files[i] == path then
+ current = i
+ break
+ end
+ end
+ if current == nil then
+ return
+ end
+ msg.trace("current file position in files: " .. current)
+
+ -- treat already existing playlist entries, independent of how they got added
+ -- as if they got added by autoload
+ for _, entry in ipairs(pl) do
+ added_entries[entry.filename] = true
+ end
+
+ local append = { [-1] = {}, [1] = {} }
+ for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
+ for i = 1, o.autoload_max_entries do
+ local pos = current + i * direction
+ local file = files[pos]
+ if file == nil or file[1] == "." then
+ break
+ end
+
+ -- skip files that are/were already in the playlist
+ if not added_entries[file] then
+ if direction == -1 then
+ msg.info("Prepending " .. file)
+ table.insert(append[-1], 1, { file, pl_current + i * direction + 1 })
+ else
+ msg.info("Adding " .. file)
+ if pl_count > 1 then
+ table.insert(append[1], { file, pl_current + i * direction - 1 })
+ else
+ mp.commandv("loadfile", file, "append")
+ end
+ end
+ end
+ added_entries[file] = true
+ end
+ if pl_count == 1 and direction == -1 and #append[-1] > 0 then
+ for i = 1, #append[-1] do
+ mp.commandv("loadfile", append[-1][i][1], "append")
+ end
+ mp.commandv("playlist-move", 0, current)
+ end
+ end
+
+ if pl_count > 1 then
+ add_files(append[1])
+ add_files(append[-1])
+ end
+end
+
+--modified fork of chapterskip.lua--
+
+function matches(i, title)
+ local opt_skip = o.skip
+ if type(o.skip) == "table" then
+ for i = 1, #o.skip do
+ if o.skip[i] and o.skip[i][1] == chapter_state then
+ opt_skip = o.skip[i][2]
+ break
+ end
+ end
+ end
+
+ for category in string.gmatch(opt_skip, " *([^;]*[^; ]) *") do
+ if categories[category:lower()] then
+ if category:lower() == "idx-" or category:lower() == "toggle_idx" then
+ for pattern in string.gmatch(categories[category:lower()], "([^/]+)") do
+ if tonumber(pattern) == i then
+ return true
+ end
+ end
+ else
+ if title then
+ for pattern in string.gmatch(categories[category:lower()], "([^/]+)") do
+ if string.match(title, pattern) then
+ return true
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+local skipped = {}
+local parsed = {}
+
+function prep_chapterskip_var()
+ if chapter_state == "no-chapters" then
+ return
+ end
+ g_opt_categories = o.categories
+
+ g_opt_skip_once = false
+ if o.skip_once == true or o.skip_once == false then
+ g_opt_skip_once = o.skip_once
+ elseif has_value(o.skip_once, chapter_state) then
+ g_opt_skip_once = true
+ end
+
+ if type(o.categories) == "table" then
+ for i = 1, #o.categories do
+ if o.categories[i] and o.categories[i][1] == chapter_state then
+ g_opt_categories = o.categories[i][2]
+ break
+ end
+ end
+ end
+
+ for category in string.gmatch(g_opt_categories, "([^;]+)") do
+ local name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)")
+ if name then
+ categories[name:lower()] = patterns
+ elseif not parsed[category] then
+ mp.msg.warn("Improper category definition: " .. category)
+ end
+ parsed[category] = true
+ end
+end
+
+function start_chapterskip_countdown(text, duration)
+ g_autoskip_countdown_flag = true
+ g_autoskip_countdown = g_autoskip_countdown - 1
+
+ if o.autoskip_countdown_graceful and (g_autoskip_countdown <= 0) then
+ kill_chapterskip_countdown()
+ mp.osd_message("", 0)
+ return
+ end
+
+ if g_autoskip_countdown < 0 then
+ kill_chapterskip_countdown()
+ mp.osd_message("", 0)
+ return
+ end
+
+ text = text:gsub("%%countdown%%", g_autoskip_countdown)
+ prompt_msg(text, 2000)
+end
+
+function kill_chapterskip_countdown(action)
+ if not g_autoskip_countdown_flag then
+ return
+ end
+ if action == "osd" and o.autoskip_osd ~= "no-osd" then
+ prompt_msg("○ Auto-Skip Cancelled")
+ end
+ if g_autoskip_timer ~= nil then
+ g_autoskip_timer:kill()
+ end
+ unbind_keys(o.cancel_autoskip_countdown_keybind, "cancel-autoskip-countdown")
+ unbind_keys(o.proceed_autoskip_countdown_keybind, "proceed-autoskip-countdown")
+ g_autoskip_countdown = o.autoskip_countdown
+ g_autoskip_countdown_flag = false
+end
+
+function chapterskip(_, current, countdown)
+ if chapter_state == "no-chapters" then
+ return
+ end
+ if not autoskip_chapter then
+ return
+ end
+ if g_autoskip_countdown_flag then
+ kill_chapterskip_countdown("osd")
+ end
+ if not countdown then
+ countdown = o.autoskip_countdown
+ end
+
+ local chapters = mp.get_property_native("chapter-list")
+ local skip = false
+ local consecutive_i = 0
+
+ for i = 0, #chapters do
+ if
+ (not g_opt_skip_once or not skipped[i])
+ and i == 0
+ and chapters[i + 1]
+ and matches(i, chapters[i + 1].title)
+ then
+ if i == current + 1 or skip == i - 1 then
+ if skip then
+ skipped[skip] = true
+ end
+ skip = i
+ consecutive_i = consecutive_i + 1
+ end
+ elseif (not g_opt_skip_once or not skipped[i]) and chapters[i] and matches(i, chapters[i].title) then
+ if i == current + 1 or skip == i - 1 then
+ if skip then
+ skipped[skip] = true
+ end
+ skip = i
+ consecutive_i = consecutive_i + 1
+ end
+ elseif skip and countdown <= 0 then
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(autoskip_osd, "show-progress")
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+
+ if o.autoskip_osd == "osd-msg-bar" or o.autoskip_osd == "osd-msg" then
+ if consecutive_i > 1 then
+ local autoskip_osd_string = ""
+ for j = consecutive_i, 1, -1 do
+ local chapter_title = ""
+ if chapters[i - j] then
+ chapter_title = chapters[i - j].title
+ end
+ autoskip_osd_string = (
+ autoskip_osd_string
+ .. "\n ➤ Chapter ("
+ .. i - j
+ .. ") "
+ .. chapter_title
+ )
+ end
+ prompt_msg("● Auto-Skip" .. autoskip_osd_string)
+ else
+ prompt_msg("➤ Auto-Skip: Chapter " .. mp.command_native({ "expand-text", "${chapter}" }))
+ end
+ end
+ mp.set_property("time-pos", chapters[i].time)
+ skipped[skip] = true
+ return
+ elseif skip and countdown > 0 then
+ g_autoskip_countdown_flag = true
+ bind_keys(o.cancel_autoskip_countdown_keybind, "cancel-autoskip-countdown", function()
+ kill_chapterskip_countdown("osd")
+ return
+ end)
+
+ local autoskip_osd_string = ""
+ local autoskip_graceful_osd = ""
+ if o.autoskip_countdown_graceful then
+ autoskip_graceful_osd = "Press Keybind to:\n"
+ end
+ if o.autoskip_osd == "osd-msg-bar" or o.autoskip_osd == "osd-msg" then
+ if consecutive_i > 1 and o.autoskip_countdown_bulk then
+ local autoskip_osd_string = ""
+ for j = consecutive_i, 1, -1 do
+ local chapter_title = ""
+ if chapters[i - j] then
+ chapter_title = chapters[i - j].title
+ end
+ autoskip_osd_string = (
+ autoskip_osd_string
+ .. "\n ▷ Chapter ("
+ .. i - j
+ .. ") "
+ .. chapter_title
+ )
+ end
+ prompt_msg(
+ autoskip_graceful_osd
+ .. "○ Auto-Skip"
+ .. ' in "'
+ .. o.autoskip_countdown
+ .. '"'
+ .. autoskip_osd_string,
+ 2000
+ )
+ g_autoskip_timer = mp.add_periodic_timer(1, function()
+ start_chapterskip_countdown(
+ autoskip_graceful_osd .. "○ Auto-Skip" .. ' in "%countdown%"' .. autoskip_osd_string,
+ 2000
+ )
+ end)
+ else
+ prompt_msg(
+ autoskip_graceful_osd
+ .. '▷ Auto-Skip in "'
+ .. o.autoskip_countdown
+ .. '": Chapter '
+ .. mp.command_native({ "expand-text", "${chapter}" }),
+ 2000
+ )
+ g_autoskip_timer = mp.add_periodic_timer(1, function()
+ start_chapterskip_countdown(
+ autoskip_graceful_osd
+ .. '▷ Auto-Skip in "%countdown%": Chapter '
+ .. mp.command_native({ "expand-text", "${chapter}" }),
+ 2000
+ )
+ end)
+ end
+ end
+ function proceed_autoskip(force)
+ if not g_autoskip_countdown_flag then
+ kill_chapterskip_countdown()
+ return
+ end
+ if g_autoskip_countdown > 1 and not force then
+ return
+ end
+
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(autoskip_osd, "show-progress")
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ if o.autoskip_osd == "osd-msg-bar" or o.autoskip_osd == "osd-msg" then
+ if consecutive_i > 1 and o.autoskip_countdown_bulk then
+ local autoskip_osd_string = ""
+ for j = consecutive_i, 1, -1 do
+ local chapter_title = ""
+ if chapters[i - j] then
+ chapter_title = chapters[i - j].title
+ end
+ autoskip_osd_string = (
+ autoskip_osd_string
+ .. "\n ➤ Chapter ("
+ .. i - j
+ .. ") "
+ .. chapter_title
+ )
+ end
+ prompt_msg("● Auto-Skip" .. autoskip_osd_string)
+ else
+ prompt_msg("➤ Auto-Skip: Chapter " .. mp.command_native({ "expand-text", "${chapter}" }))
+ end
+ end
+ if consecutive_i > 1 and o.autoskip_countdown_bulk then
+ mp.set_property("time-pos", chapters[i].time)
+ else
+ mp.commandv("no-osd", "add", "chapter", 1)
+ end
+ skipped[skip] = true
+ kill_chapterskip_countdown()
+ end
+ bind_keys(o.proceed_autoskip_countdown_keybind, "proceed-autoskip-countdown", function()
+ proceed_autoskip(true)
+ return
+ end)
+ if o.autoskip_countdown_graceful then
+ return
+ end
+ mp.add_timeout(countdown, proceed_autoskip)
+ return
+ end
+ end
+ if skip and countdown <= 0 then
+ if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then
+ return mp.set_property("time-pos", mp.get_property_native("duration"))
+ end
+ mp.commandv("playlist-next")
+ if o.autoskip_osd ~= "no-osd" then
+ autoskip_playlist_osd = true
+ end
+ elseif skip and countdown > 0 then
+ g_autoskip_countdown_flag = true
+ bind_keys(o.cancel_autoskip_countdown_keybind, "cancel-autoskip-countdown", function()
+ kill_chapterskip_countdown("osd")
+ return
+ end)
+
+ if o.autoskip_osd == "osd-msg-bar" or o.autoskip_osd == "osd-msg" then
+ local autoskip_graceful_osd = ""
+ if o.autoskip_countdown_graceful then
+ autoskip_graceful_osd = "Press Keybind to:\n"
+ end
+ if consecutive_i > 1 and o.autoskip_countdown_bulk then
+ local i = (mp.get_property_number("chapters") + 1 or 0)
+ local autoskip_osd_string = ""
+ for j = consecutive_i, 1, -1 do
+ local chapter_title = ""
+ if chapters[i - j] then
+ chapter_title = chapters[i - j].title
+ end
+ autoskip_osd_string = (autoskip_osd_string .. "\n ▷ Chapter (" .. i - j .. ") " .. chapter_title)
+ end
+ prompt_msg(
+ autoskip_graceful_osd
+ .. "○ Auto-Skip"
+ .. ' in "'
+ .. o.autoskip_countdown
+ .. '"'
+ .. autoskip_osd_string,
+ 2000
+ )
+ g_autoskip_timer = mp.add_periodic_timer(1, function()
+ start_chapterskip_countdown(
+ autoskip_graceful_osd .. "○ Auto-Skip" .. ' in "%countdown%"' .. autoskip_osd_string,
+ 2000
+ )
+ end)
+ else
+ prompt_msg(
+ autoskip_graceful_osd
+ .. '▷ Auto-Skip in "'
+ .. o.autoskip_countdown
+ .. '": Chapter '
+ .. mp.command_native({ "expand-text", "${chapter}" }),
+ 2000
+ )
+ g_autoskip_timer = mp.add_periodic_timer(1, function()
+ start_chapterskip_countdown(
+ autoskip_graceful_osd
+ .. '▷ Auto-Skip in "%countdown%": Chapter '
+ .. mp.command_native({ "expand-text", "${chapter}" }),
+ 2000
+ )
+ end)
+ end
+ end
+ function proceed_autoskip(force)
+ if not g_autoskip_countdown_flag then
+ return
+ end
+ if g_autoskip_countdown > 1 and not force then
+ return
+ end
+
+ mp.set_property("osd-duration", o.osd_duration)
+ mp.commandv(autoskip_osd, "show-progress")
+ mp.add_timeout(0.07, function()
+ mp.set_property("osd-duration", osd_duration_default)
+ end)
+ if consecutive_i > 1 and o.autoskip_countdown_bulk then
+ if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then
+ return mp.set_property("time-pos", mp.get_property_native("duration"))
+ end
+ mp.commandv("playlist-next")
+ else
+ local current_chapter = (mp.get_property_number("chapter") + 1 or 0)
+ local chapters_count = (mp.get_property_number("chapters") or 0)
+
+ if current_chapter == chapters_count then
+ if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then
+ return mp.set_property("time-pos", mp.get_property_native("duration"))
+ end
+ mp.commandv("playlist-next")
+ else
+ mp.commandv("no-osd", "add", "chapter", 1)
+ end
+ end
+ if o.autoskip_osd ~= "no-osd" then
+ autoskip_playlist_osd = true
+ end
+ kill_chapterskip_countdown()
+ end
+ bind_keys(o.proceed_autoskip_countdown_keybind, "proceed-autoskip-countdown", function()
+ proceed_autoskip(true)
+ return
+ end)
+ if o.autoskip_countdown_graceful then
+ return
+ end
+ mp.add_timeout(countdown, proceed_autoskip)
+ end
+end
+
+function toggle_autoskip()
+ if autoskip_chapter == true then
+ prompt_msg("○ Auto-Skip Disabled")
+ autoskip_chapter = false
+ if g_autoskip_countdown_flag then
+ kill_chapterskip_countdown()
+ end
+ elseif autoskip_chapter == false then
+ prompt_msg("● Auto-Skip Enabled")
+ autoskip_chapter = true
+ end
+end
+
+function toggle_category_autoskip()
+ if chapter_state == "no-chapters" then
+ return
+ end
+ if not mp.get_property_number("chapter") then
+ return
+ end
+ local chapters = mp.get_property_native("chapter-list")
+ local current_chapter = (mp.get_property_number("chapter") + 1 or 0)
+
+ local chapter_title = tostring(current_chapter)
+ if current_chapter > 0 and chapters[current_chapter].title and chapters[current_chapter].title ~= "" then
+ chapter_title = chapters[current_chapter].title
+ end
+
+ local found_i = 0
+ if matches(current_chapter, chapter_title) then
+ for category in string.gmatch(g_opt_categories, "([^;]+)") do
+ local name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)")
+
+ for pattern in string.gmatch(patterns, "([^/]+)") do
+ if string.match(chapter_title:lower(), pattern:lower()) then
+ g_opt_categories = g_opt_categories:gsub(esc_string(pattern) .. "/?", "")
+ found_i = found_i + 1
+ end
+ end
+ end
+
+ for category in string.gmatch(g_opt_categories, "([^;]+)") do
+ local name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)")
+ if name then
+ categories[name:lower()] = patterns
+ elseif not parsed[category] then
+ mp.msg.warn("Improper category definition: " .. category)
+ end
+ parsed[category] = true
+ end
+
+ if type(o.categories) == "table" then
+ for i = 1, #o.categories do
+ if o.categories[i] and o.categories[i][1] == chapter_state then
+ o.categories[i][2] = g_opt_categories
+ break
+ end
+ end
+ else
+ o.categories = g_opt_categories
+ end
+ end
+ if current_chapter > 0 and chapters[current_chapter].title and chapters[current_chapter].title ~= "" then
+ if found_i > 0 or string.match(categories.toggle, esc_string(chapter_title)) then
+ prompt_msg("○ Removed from Auto-Skip\n ▷ Chapter: " .. chapter_title)
+ categories.toggle = categories.toggle:gsub(esc_string("^" .. chapter_title .. "/"), "")
+ if g_autoskip_countdown_flag then
+ kill_chapterskip_countdown()
+ end
+ else
+ prompt_msg("● Added to Auto-Skip\n ➔ Chapter: " .. chapter_title)
+ categories.toggle = categories.toggle .. "^" .. chapter_title .. "/"
+ end
+ else
+ if found_i > 0 or string.match(categories.toggle_idx, esc_string(chapter_title)) then
+ prompt_msg("○ Removed from Auto-Skip\n ▷ Chapter: " .. chapter_title)
+ categories.toggle_idx = categories.toggle_idx:gsub(esc_string(chapter_title .. "/"), "")
+ if g_autoskip_countdown_flag then
+ kill_chapterskip_countdown()
+ end
+ else
+ prompt_msg("● Added to Auto-Skip\n ➔ Chapter: " .. chapter_title)
+ categories.toggle_idx = categories.toggle_idx .. chapter_title .. "/"
+ end
+ end
+end
+
+-- HOOKS --------------------------------------------------------------------
+if user_input_module then
+ mp.add_hook("on_unload", 50, function()
+ input.cancel_user_input()
+ end)
+end -- chapters.lua
+mp.register_event("start-file", find_and_add_entries) -- autoload.lua
+mp.observe_property("chapter", "number", chapterskip) -- chapterskip.lua
+
+-- smart skip events / properties / hooks --
+
+mp.register_event("file-loaded", function()
+ file_length = (mp.get_property_native("duration") or 0)
+ if o.playlist_osd and g_playlist_pos > 0 then
+ playlist_osd = true
+ end
+ if playlist_osd and not autoskip_playlist_osd then
+ prompt_msg(
+ "["
+ .. mp.command_native({ "expand-text", "${playlist-pos-1}" })
+ .. "/"
+ .. mp.command_native({ "expand-text", "${playlist-count}" })
+ .. "] "
+ .. mp.command_native({ "expand-text", "${filename}" })
+ )
+ end
+ if autoskip_playlist_osd then
+ prompt_msg(
+ "➤ Auto-Skip\n["
+ .. mp.command_native({ "expand-text", "${playlist-pos-1}" })
+ .. "/"
+ .. mp.command_native({ "expand-text", "${playlist-count}" })
+ .. "] "
+ .. mp.command_native({ "expand-text", "${filename}" })
+ )
+ end
+ playlist_osd = false
+ autoskip_playlist_osd = false
+ force_silence_skip = false
+ skipped = {}
+ initial_chapter_count = mp.get_property_number("chapter-list/count")
+ if initial_chapter_count > 0 and chapter_state ~= "external-chapters" then
+ chapter_state = "internal-chapters"
+ end
+ prep_chapterskip_var()
+end)
+
+mp.add_hook("on_load", 50, function()
+ if o.external_chapters_autoload then
+ load_chapters()
+ end
+end)
+
+mp.observe_property("pause", "bool", function(name, value)
+ if value and skip_flag then
+ restoreProp(initial_skip_time, true)
+ end
+ if g_autoskip_countdown_flag then
+ kill_chapterskip_countdown("osd")
+ end
+end)
+
+mp.add_hook("on_unload", 9, function()
+ if o.modified_chapters_autosave == true or has_value(o.modified_chapters_autosave, chapter_state) then
+ write_chapters(false)
+ end
+ mp.set_property("keep-open", keep_open_state)
+ chapter_state = "no-chapters"
+ g_playlist_pos = (mp.get_property_native("playlist-playing-pos") + 1 or 0)
+ kill_chapterskip_countdown()
+end)
+
+mp.register_event("seek", function()
+ if g_autoskip_countdown_flag then
+ kill_chapterskip_countdown("osd")
+ end
+end)
+
+mp.observe_property("eof-reached", "bool", eofHandler)
+
+-- BINDINGS --------------------------------------------------------------------
+
+bind_keys(o.toggle_autoload_keybind, "toggle-autoload", toggle_autoload)
+bind_keys(o.toggle_autoskip_keybind, "toggle-autoskip", toggle_autoskip)
+bind_keys(o.toggle_category_autoskip_keybind, "toggle-category-autoskip", toggle_category_autoskip)
+bind_keys(o.add_chapter_keybind, "add-chapter", add_chapter)
+bind_keys(o.remove_chapter_keybind, "remove-chapter", remove_chapter)
+bind_keys(o.write_chapters_keybind, "write-chapters", function()
+ write_chapters(true)
+end)
+bind_keys(o.edit_chapter_keybind, "edit-chapter", edit_chapter)
+bind_keys(o.bake_chapters_keybind, "bake-chapters", bake_chapters)
+bind_keys(o.chapter_prev_keybind, "chapter-prev", function()
+ chapterSeek(-1)
+end)
+bind_keys(o.chapter_next_keybind, "chapter-next", function()
+ chapterSeek(1)
+end)
+bind_keys(o.smart_prev_keybind, "smart-prev", smartPrev)
+bind_keys(o.smart_next_keybind, "smart-next", smartNext)
+bind_keys(o.silence_skip_keybind, "silence-skip", silenceSkip)
diff --git a/ar/.config/mpv/scripts/UndoRedo.lua b/ar/.config/mpv/scripts/UndoRedo.lua
new file mode 100644
index 0000000..67d3f2b
--- /dev/null
+++ b/ar/.config/mpv/scripts/UndoRedo.lua
@@ -0,0 +1,212 @@
+-- Copyright (c) 2021, Eisa AlAwadhi
+-- License: BSD 2-Clause License
+
+-- Creator: Eisa AlAwadhi
+-- Project: UndoRedo
+-- Version: 2.2
+
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+local seconds = 0
+local countTimer = -1
+local seekTime = 0
+local seekNumber = 0
+local currentIndex = 0
+local seekTable = {}
+local seeking = 0
+local undoRedo = 0
+local pause = false
+seekTable[0] = 0
+
+----------------------------USER CUSTOMIZATION SETTINGS-----------------------------------
+--These settings are for users to manually change some options in the script.
+--Keybinds can be defined in the bottom of the script.
+
+local osd_messages = true --true is for displaying osd messages when actions occur, Change to false will disable all osd messages generated from this script
+
+---------------------------END OF USER CUSTOMIZATION SETTINGS---------------------
+
+local function prepareUndoRedo()
+ if pause == true then
+ seconds = seconds
+ else
+ seconds = seconds - 0.5
+ end
+ seekTable[currentIndex] = seekTable[currentIndex] + seconds
+ seconds = 0
+end
+
+local function getUndoRedo()
+ if seeking == 0 then
+ prepareUndoRedo()
+
+ seekNumber = currentIndex + 1
+ currentIndex = seekNumber
+ seekTime = math.floor(mp.get_property_number("time-pos"))
+ table.insert(seekTable, seekNumber, seekTime)
+
+ undoRedo = 0
+ elseif seeking == 1 then
+ seeking = 0
+ end
+end
+
+mp.register_event("file-loaded", function()
+ filePath = mp.get_property("path")
+
+ timer = mp.add_periodic_timer(0.1, function()
+ seconds = seconds + 0.1
+ end)
+
+ if pause == true then
+ timer:stop()
+ else
+ timer:resume()
+ end
+
+ timer2 = mp.add_periodic_timer(0.1, function()
+ countTimer = countTimer + 0.1
+
+ if countTimer == 0.6 then
+ timer:resume()
+ getUndoRedo()
+ end
+ end)
+
+ timer2:stop()
+end)
+
+mp.register_event("seek", function()
+ countTimer = 0
+ timer2:resume()
+ timer:stop()
+end)
+
+mp.observe_property("pause", "bool", function(name, value)
+ if value then
+ if timer ~= nil then
+ timer:stop()
+ end
+ pause = true
+ else
+ if timer ~= nil then
+ timer:resume()
+ end
+ pause = false
+ end
+end)
+
+mp.register_event("end-file", function()
+ if timer ~= nil then
+ timer:kill()
+ end
+ if timer2 ~= nil then
+ timer2:kill()
+ end
+ seekNumber = 0
+ currentIndex = 0
+ undoRedo = 0
+ seconds = 0
+ countTimer = -1
+ seekTable[0] = 0
+end)
+
+local function undo()
+ if (filePath ~= nil) and (countTimer >= 0) and (countTimer < 0.6) and (seeking == 0) then
+ timer2:stop()
+
+ getUndoRedo()
+
+ currentIndex = currentIndex - 1
+ if currentIndex < 0 then
+ if osd_messages == true then
+ mp.osd_message("No Undo Found")
+ end
+ currentIndex = 0
+ msg.info("No undo found")
+ else
+ if seekTable[currentIndex] < 0 then
+ seekTable[currentIndex] = 0
+ end
+
+ seeking = 1
+
+ mp.commandv("seek", seekTable[currentIndex], "absolute", "exact")
+
+ undoRedo = 1
+ if osd_messages == true then
+ mp.osd_message("Undo")
+ end
+ msg.info("Seeked using undo")
+ end
+ elseif (filePath ~= nil) and (currentIndex > 0) then
+ prepareUndoRedo()
+ currentIndex = currentIndex - 1
+
+ if seekTable[currentIndex] < 0 then
+ seekTable[currentIndex] = 0
+ end
+
+ seeking = 1
+ mp.commandv("seek", seekTable[currentIndex], "absolute", "exact")
+
+ undoRedo = 1
+ if osd_messages == true then
+ mp.osd_message("Undo")
+ end
+ msg.info("Seeked using undo")
+ elseif (filePath ~= nil) and (currentIndex == 0) then
+ if osd_messages == true then
+ mp.osd_message("No Undo Found")
+ end
+ msg.info("No undo found")
+ end
+end
+
+local function redo()
+ if (filePath ~= nil) and (currentIndex < seekNumber) then
+ prepareUndoRedo()
+ currentIndex = currentIndex + 1
+
+ if seekTable[currentIndex] < 0 then
+ seekTable[currentIndex] = 0
+ end
+
+ seeking = 1
+ mp.commandv("seek", seekTable[currentIndex], "absolute", "exact")
+
+ undoRedo = 0
+
+ if osd_messages == true then
+ mp.osd_message("Redo")
+ end
+ msg.info("Seeked using redo")
+ elseif (filePath ~= nil) and (currentIndex == seekNumber) then
+ if osd_messages == true then
+ mp.osd_message("No Redo Found")
+ end
+ msg.info("No redo found")
+ end
+end
+
+local function undoLoop()
+ if (filePath ~= nil) and (undoRedo == 0) then
+ undo()
+ elseif (filePath ~= nil) and (undoRedo == 1) then
+ redo()
+ elseif (filePath ~= nil) and (countTimer == -1) then
+ if osd_messages == true then
+ mp.osd_message("No Undo Found")
+ end
+ msg.info("No undo found")
+ end
+end
+
+mp.add_key_binding("ctrl+z", "undo", undo)
+mp.add_key_binding("ctrl+Z", "undoCaps", undo)
+
+mp.add_key_binding("ctrl+y", "redo", redo)
+mp.add_key_binding("ctrl+Y", "redoCaps", redo)
+
+mp.add_key_binding("ctrl+alt+z", "undoLoop", undoLoop)
+mp.add_key_binding("ctrl+alt+Z", "undoLoopCaps", undoLoop)
diff --git a/ar/.config/mpv/scripts/blackout.lua b/ar/.config/mpv/scripts/blackout.lua
new file mode 100644
index 0000000..419f34d
--- /dev/null
+++ b/ar/.config/mpv/scripts/blackout.lua
@@ -0,0 +1,73 @@
+--[[
+ blackout / by sibwaf / https://github.com/sibwaf/mpv-scripts
+
+ Turns the screen completely black and pauses on a button press ([b] by default)
+ so you can hide whatever you are watching from other people fast enough.
+ Unpause or press the same button to go back.
+
+ May not work if your VO driver doesn't support changing contrast.
+
+ MIT license - do whatever you want, but I'm not responsible for any possible problems.
+ Please keep the URL to the original repository. Thanks!
+]]
+
+--[[
+ Configuration:
+
+ # property
+
+ Theoratically, can be any of: "brightness", "contrast", "saturation", "gamma", "hue".
+ In practice, "contrast" seems to work best. Setting it to "saturation" or "hue"
+ is pretty much pointless as you will get gray-scale or a colored negative video.
+]]
+
+local property = "contrast"
+
+----------
+
+local watch_later_options_default = mp.get_property_native("watch-later-options")
+local watch_later_options_blackout = {}
+
+for _, option in pairs(watch_later_options_default) do
+ if option == property then
+ -- remove
+ elseif option == "sid" then
+ -- remove
+ else
+ table.insert(watch_later_options_blackout, option)
+ end
+end
+
+local saved_value = nil
+local saved_sid = nil
+
+function toggle_blackout()
+ if saved_value then
+ mp.set_property(property, saved_value)
+ saved_value = nil
+
+ mp.set_property("sid", saved_sid)
+ saved_sid = nil
+
+ mp.set_property_native("watch-later-options", watch_later_options_default)
+ else
+ mp.set_property("pause", "yes")
+
+ saved_value = mp.get_property(property)
+ mp.set_property(property, -100)
+
+ saved_sid = mp.get_property("sid")
+ mp.set_property("sid", "no")
+
+ mp.set_property_native("watch-later-options", watch_later_options_blackout)
+ end
+end
+
+function on_pause_change(name, value)
+ if not value and saved_value then
+ toggle_blackout()
+ end
+end
+
+mp.add_key_binding("b", "blackout", toggle_blackout)
+mp.observe_property("pause", "bool", on_pause_change)
diff --git a/ar/.config/mpv/scripts/blur-edges.lua b/ar/.config/mpv/scripts/blur-edges.lua
new file mode 100644
index 0000000..6c3a111
--- /dev/null
+++ b/ar/.config/mpv/scripts/blur-edges.lua
@@ -0,0 +1,174 @@
+local options = require("mp.options")
+
+local opts = {
+ blur_radius = 10,
+ blur_power = 10,
+ minimum_black_bar_size = 3,
+ mode = "all",
+ active = true,
+ reapply_delay = 0.5,
+ watch_later_fix = false,
+ only_fullscreen = true,
+}
+options.read_options(opts)
+
+local active = opts.active
+local applied = false
+
+function set_lavfi_complex(filter)
+ if not filter and mp.get_property("lavfi-complex") == "" then
+ return
+ end
+ local force_window = mp.get_property("force-window")
+ local sub = mp.get_property("sub")
+ mp.set_property("force-window", "yes")
+ if not filter then
+ mp.set_property("lavfi-complex", "")
+ mp.set_property("vid", "1")
+ else
+ if not opts.watch_later_fix then
+ mp.set_property("vid", "no")
+ end
+ mp.set_property("lavfi-complex", filter)
+ end
+ mp.set_property("sub", "no")
+ mp.set_property("force-window", force_window)
+ mp.set_property("sub", sub)
+end
+
+function set_blur()
+ if applied then
+ return
+ end
+ if not mp.get_property("video-out-params") then
+ return
+ end
+ if opts.only_fullscreen and not mp.get_property_bool("fullscreen") then
+ return
+ end
+ local video_aspect = mp.get_property_number("video-aspect-override")
+ local ww, wh = mp.get_osd_size()
+
+ if math.abs(ww / wh - video_aspect) < 0.05 then
+ return
+ end
+ if opts.mode == "horizontal" and ww / wh < video_aspect then
+ return
+ end
+ if opts.mode == "vertical" and ww / wh > video_aspect then
+ return
+ end
+
+ local par = mp.get_property_number("video-params/par")
+ local height = mp.get_property_number("video-params/h")
+ local width = mp.get_property_number("video-params/w")
+
+ local split = "[vid1] split=3 [a] [v] [b]"
+ local crop_format = "crop=%s:%s:%s:%s"
+ local scale_format = "scale=width=%s:height=%s:flags=neighbor"
+
+ local stack_direction, cropped_scaled_1, cropped_scaled_2, blur_size
+
+ if ww / wh > video_aspect then
+ blur_size = math.floor((ww / wh) * height / par - width)
+ local nudge = blur_size % 2
+ blur_size = blur_size / 2
+
+ local height_with_maximized_width = height / width * ww
+ local visible_height = math.floor(height * par * wh / height_with_maximized_width)
+ local visible_width = math.floor(blur_size * wh / height_with_maximized_width)
+
+ local cropped_1 = string.format(crop_format, visible_width, visible_height, "0", (height - visible_height) / 2)
+ local scaled_1 = string.format(scale_format, blur_size + nudge, height)
+ cropped_scaled_1 = cropped_1 .. "," .. scaled_1
+
+ local cropped_2 = string.format(
+ crop_format,
+ visible_width,
+ visible_height,
+ width - visible_width,
+ (height - visible_height) / 2
+ )
+ local scaled_2 = string.format(scale_format, blur_size, height)
+ cropped_scaled_2 = cropped_2 .. "," .. scaled_2
+ stack_direction = "h"
+ else
+ blur_size = math.floor((wh / ww) * width * par - height)
+ local nudge = blur_size % 2
+ blur_size = blur_size / 2
+
+ local width_with_maximized_height = width / height * wh
+ local visible_width = math.floor(width * ww / width_with_maximized_height)
+ local visible_height = math.floor(blur_size * ww / width_with_maximized_height)
+
+ local cropped_1 = string.format(crop_format, visible_width, visible_height, (width - visible_width) / 2, "0")
+ local scaled_1 = string.format(scale_format, width, blur_size + nudge)
+ cropped_scaled_1 = cropped_1 .. "," .. scaled_1
+
+ local cropped_2 = string.format(
+ crop_format,
+ visible_width,
+ visible_height,
+ (width - visible_width) / 2,
+ height - visible_height
+ )
+ local scaled_2 = string.format(scale_format, width, blur_size)
+ cropped_scaled_2 = cropped_2 .. "," .. scaled_2
+ stack_direction = "v"
+ end
+
+ if blur_size < math.max(1, opts.minimum_black_bar_size) then
+ return
+ end
+ local lr = math.min(opts.blur_radius, math.floor(blur_size / 2) - 1)
+ local cr = math.min(opts.blur_radius, math.floor(blur_size / 4) - 1)
+ local blur = string.format("boxblur=lr=%i:lp=%i:cr=%i:cp=%i", lr, opts.blur_power, cr, opts.blur_power)
+
+ zone_1 = string.format("[a] %s,%s [a_fin]", cropped_scaled_1, blur)
+ zone_2 = string.format("[b] %s,%s [b_fin]", cropped_scaled_2, blur)
+
+ local par_fix = "setsar=ratio=" .. tostring(par) .. ":max=10000"
+
+ stack = string.format("[a_fin] [v] [b_fin] %sstack=3,%s [vo]", stack_direction, par_fix)
+ filter = string.format("%s;%s;%s;%s", split, zone_1, zone_2, stack)
+ set_lavfi_complex(filter)
+ applied = true
+end
+
+function unset_blur()
+ set_lavfi_complex()
+ applied = false
+end
+
+local reapplication_timer = mp.add_timeout(opts.reapply_delay, set_blur)
+reapplication_timer:kill()
+
+function reset_blur(k, v)
+ unset_blur()
+ reapplication_timer:kill()
+ reapplication_timer:resume()
+end
+
+function toggle()
+ if active then
+ active = false
+ unset_blur()
+ mp.unobserve_property(reset_blur)
+ else
+ active = true
+ set_blur()
+ local properties = { "osd-width", "osd-height", "path", "fullscreen" }
+ for _, p in ipairs(properties) do
+ mp.observe_property(p, "native", reset_blur)
+ end
+ end
+end
+
+if active then
+ active = false
+ toggle()
+end
+
+mp.add_key_binding(nil, "toggle-blur", toggle)
+mp.add_key_binding(nil, "set-blur", set_blur)
+mp.add_key_binding(nil, "unset-blur", unset_blur)
diff --git a/ar/.config/mpv/scripts/change-OSD-media-title.lua b/ar/.config/mpv/scripts/change-OSD-media-title.lua
new file mode 100644
index 0000000..3e4332d
--- /dev/null
+++ b/ar/.config/mpv/scripts/change-OSD-media-title.lua
@@ -0,0 +1,36 @@
+function set_osd_title()
+ local name = mp.get_property_osd("filename")
+ local percent_pos = ""
+ local chapter = ""
+ local frames_dropped = ""
+
+ if mp.get_property_osd("percent-pos") ~= "" then
+ percent_pos = mp.get_property_osd("percent-pos") .. "% completed | "
+ end
+
+ if mp.get_property_osd("chapter") ~= "" then
+ chapter = "Chapter: " .. mp.get_property_osd("chapter") .. " | "
+ end
+
+ if mp.get_property_osd("playlist-count") ~= "1" then
+ playlist_num = "["
+ .. mp.get_property_osd("playlist-pos-1")
+ .. "/"
+ .. mp.get_property_osd("playlist-count")
+ .. "] "
+ end
+ if mp.get_property_osd("frame-drop-count") ~= "0" then
+ if mp.get_property_osd("frame-drop-count") == "1" then
+ frames_dropped = mp.get_property_osd("frame-drop-count") .. " dropped frame | "
+ else
+ frames_dropped = mp.get_property_osd("frame-drop-count") .. " dropped frames | "
+ end
+ mp.set_property("force-media-title", playlist_num .. chapter .. percent_pos .. frames_dropped .. name)
+ else
+ mp.set_property("force-media-title", playlist_num .. chapter .. percent_pos .. name)
+ end
+end
+
+mp.observe_property("percent-pos", "number", set_osd_title)
+mp.observe_property("chapter", "string", set_osd_title)
+mp.observe_property("frame-drop-count", "number", set_osd_title)
diff --git a/ar/.config/mpv/scripts/command_palette.lua b/ar/.config/mpv/scripts/command_palette.lua
new file mode 100644
index 0000000..adbd330
--- /dev/null
+++ b/ar/.config/mpv/scripts/command_palette.lua
@@ -0,0 +1,1229 @@
+-- https://github.com/stax76/mpv-scripts
+
+----- options
+
+local o = {
+ font_size = 16,
+ scale_by_window = false,
+ lines_to_show = 12,
+ pause_on_open = false, -- does not work on my system when enabled, menu won't show
+ resume_on_exit = "only-if-was-paused",
+
+ -- styles
+ line_bottom_margin = 1,
+ menu_x_padding = 5,
+ menu_y_padding = 2,
+
+ use_mediainfo = false, -- # true requires the MediaInfo CLI app being installed
+ stream_quality_options = "2160,1440,1080,720,480",
+ aspect_ratios = "4:3,16:9,2.35:1,1.36,1.82,0,-1",
+}
+
+local opt = require("mp.options")
+opt.read_options(o)
+
+----- string
+
+function is_empty(input)
+ if input == nil or input == "" then
+ return true
+ end
+end
+
+function contains(input, find)
+ if not is_empty(input) and not is_empty(find) then
+ return input:find(find, 1, true)
+ end
+end
+
+function starts_with(str, start)
+ return str:sub(1, #start) == start
+end
+
+function split(input, sep)
+ assert(#sep == 1) -- supports only single character separator
+ local tbl = {}
+
+ if input ~= nil then
+ for str in string.gmatch(input, "([^" .. sep .. "]+)") do
+ table.insert(tbl, str)
+ end
+ end
+
+ return tbl
+end
+
+function replace(str, what, with)
+ what = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
+ with = string.gsub(with, "[%%]", "%%%%")
+ return string.gsub(str, what, with)
+end
+
+function first_to_upper(str)
+ return (str:gsub("^%l", string.upper))
+end
+
+----- list
+
+function list_contains(list, value)
+ for _, v in pairs(list) do
+ if v == value then
+ return true
+ end
+ end
+end
+
+----- path
+
+function get_temp_dir()
+ local is_windows = package.config:sub(1, 1) == "\\"
+
+ if is_windows then
+ return os.getenv("TEMP") .. "\\"
+ else
+ return "/tmp/"
+ end
+end
+
+---- file
+
+function file_exists(path)
+ if is_empty(path) then
+ return false
+ end
+ local file = io.open(path, "r")
+
+ if file ~= nil then
+ io.close(file)
+ return true
+ end
+end
+
+function file_write(path, content)
+ local file = assert(io.open(path, "w"))
+ file:write(content)
+ file:close()
+end
+
+----- mpv
+
+local utils = require("mp.utils")
+local assdraw = require("mp.assdraw")
+local msg = require("mp.msg")
+
+----- path mpv
+
+function file_name(value)
+ local _, filename = utils.split_path(value)
+ return filename
+end
+
+----- main
+
+local command_palette_version = 2
+local BluRayTitles = {}
+local dpiScale = 0
+local originalFontSize = o.font_size
+
+mp.commandv("script-message", "command-palette-version", command_palette_version)
+
+local is_older_than_v0_36 = string.find(mp.get_property("mpv-version"), "mpv v0%.[1-3][0-5]%.") == 1
+
+if not is_older_than_v0_36 then
+ mp.set_property_native("user-data/command-palette/version", command_palette_version)
+end
+
+mp.enable_messages("info")
+
+mp.register_event("log-message", function(e)
+ if e.prefix ~= "bd" then
+ return
+ end
+
+ if contains(e.text, " 0 duration: ") then
+ BluRayTitles = {}
+ end
+
+ if contains(e.text, " duration: ") then
+ local match = string.match(e.text, "%d%d:%d%d:%d%d")
+
+ if match then
+ table.insert(BluRayTitles, match)
+ end
+ end
+end)
+
+local uosc_available = false
+package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
+
+local em = require("extended-menu")
+local menu = em:new(o)
+local menu_content = { list = {}, current_i = nil }
+local media_info_cache = {}
+local original_set_active_func = em.set_active
+local original_get_line_func = em.get_line
+
+function em:get_bindings()
+ local bindings = {
+ {
+ "esc",
+ function()
+ self:set_active(false)
+ end,
+ },
+ {
+ "enter",
+ function()
+ self:handle_enter()
+ end,
+ },
+ {
+ "bs",
+ function()
+ self:handle_backspace()
+ end,
+ },
+ {
+ "del",
+ function()
+ self:handle_del()
+ end,
+ },
+ {
+ "ins",
+ function()
+ self:handle_ins()
+ end,
+ },
+ {
+ "left",
+ function()
+ self:prev_char()
+ end,
+ },
+ {
+ "right",
+ function()
+ self:next_char()
+ end,
+ },
+ {
+ "ctrl+f",
+ function()
+ self:next_char()
+ end,
+ },
+ {
+ "up",
+ function()
+ self:change_selected_index(-1)
+ end,
+ },
+ {
+ "down",
+ function()
+ self:change_selected_index(1)
+ end,
+ },
+ {
+ "ctrl+up",
+ function()
+ self:move_history(-1)
+ end,
+ },
+ {
+ "ctrl+down",
+ function()
+ self:move_history(1)
+ end,
+ },
+ {
+ "ctrl+left",
+ function()
+ self:prev_word()
+ end,
+ },
+ {
+ "ctrl+right",
+ function()
+ self:next_word()
+ end,
+ },
+ {
+ "home",
+ function()
+ self:go_home()
+ end,
+ },
+ {
+ "end",
+ function()
+ self:go_end()
+ end,
+ },
+ {
+ "pgup",
+ function()
+ self:change_selected_index(-o.lines_to_show)
+ end,
+ },
+ {
+ "pgdwn",
+ function()
+ self:change_selected_index(o.lines_to_show)
+ end,
+ },
+ {
+ "ctrl+u",
+ function()
+ self:del_to_start()
+ end,
+ },
+ {
+ "ctrl+v",
+ function()
+ self:paste(true)
+ end,
+ },
+ {
+ "ctrl+bs",
+ function()
+ self:del_word()
+ end,
+ },
+ {
+ "ctrl+del",
+ function()
+ self:del_next_word()
+ end,
+ },
+ {
+ "kp_dec",
+ function()
+ self:handle_char_input(".")
+ end,
+ },
+ {
+ "mbtn_left",
+ function()
+ self:handle_enter()
+ end,
+ },
+ {
+ "mbtn_right",
+ function()
+ self:set_active(false)
+ end,
+ },
+ {
+ "wheel_up",
+ function()
+ self:change_selected_index(-1)
+ end,
+ },
+ {
+ "wheel_down",
+ function()
+ self:change_selected_index(1)
+ end,
+ },
+ {
+ "mbtn_forward",
+ function()
+ self:change_selected_index(-o.lines_to_show)
+ end,
+ },
+ {
+ "mbtn_back",
+ function()
+ self:change_selected_index(o.lines_to_show)
+ end,
+ },
+ }
+
+ for i = 0, 9 do
+ bindings[#bindings + 1] = {
+ "kp" .. i,
+ function()
+ self:handle_char_input("" .. i)
+ end,
+ }
+ end
+
+ return bindings
+end
+
+function em:set_active(active)
+ original_set_active_func(self, active)
+
+ if not active then
+ if osc_visibility == "auto" or osc_visibility == "always" then
+ mp.command("script-message osc-visibility " .. osc_visibility .. " no_osd")
+ osc_visibility = nil
+ elseif uosc_available then
+ mp.commandv("script-message-to", "uosc", "disable-elements", mp.get_script_name(), "")
+ end
+ end
+end
+
+menu.index_field = "index"
+
+local function format_time(t, duration)
+ local h = math.floor(t / (60 * 60))
+ t = t - (h * 60 * 60)
+ local m = math.floor(t / 60)
+ local s = t - (m * 60)
+
+ if duration >= 60 * 60 or h > 0 then
+ return string.format("%.2d:%.2d:%.2d", h, m, s)
+ end
+
+ return string.format("%.2d:%.2d", m, s)
+end
+
+function get_media_info()
+ local path = mp.get_property("path")
+
+ if contains(path, "://") or not file_exists(path) then
+ return
+ end
+
+ if media_info_cache[path] then
+ return media_info_cache[path]
+ end
+
+ local format_file = get_temp_dir() .. mp.get_script_name() .. " media-info-format-v1.txt"
+
+ if not file_exists(format_file) then
+ media_info_format =
+ [[General;N: %FileNameExtension%\\nG: %Format%, %FileSize/String%, %Duration/String%, %OverallBitRate/String%, %Recorded_Date%\\n
+Video;V: %Format%, %Format_Profile%, %Width%x%Height%, %BitRate/String%, %FrameRate% FPS\\n
+Audio;A: %Language/String%, %Format%, %Format_Profile%, %BitRate/String%, %Channel(s)% ch, %SamplingRate/String%, %Title%\\n
+Text;S: %Language/String%, %Format%, %Format_Profile%, %Title%\\n]]
+
+ file_write(format_file, media_info_format)
+ end
+
+ local proc_result = mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ args = { "mediainfo", "--inform=file://" .. format_file, path },
+ })
+
+ if proc_result.status == 0 then
+ local output = proc_result.stdout
+
+ output = string.gsub(output, ", , ,", ",")
+ output = string.gsub(output, ", ,", ",")
+ output = string.gsub(output, ": , ", ": ")
+ output = string.gsub(output, ", \\n\r*\n", "\\n")
+ output = string.gsub(output, "\\n\r*\n", "\\n")
+ output = string.gsub(output, ", \\n", "\\n")
+ output = string.gsub(output, "\\n", "\n")
+ output = string.gsub(output, "%.000 FPS", " FPS")
+ output = string.gsub(output, "MPEG Audio, Layer 3", "MP3")
+
+ media_info_cache[path] = output
+
+ return output
+ end
+end
+
+function binding_get_line(self, _, v)
+ local ass = assdraw.ass_new()
+ local cmd = self:ass_escape(v.cmd)
+ local key = self:ass_escape(v.key)
+ local comment = self:ass_escape(v.comment or "")
+
+ if v.priority == -1 or v.priority == -2 then
+ local why_inactive = (v.priority == -1) and "Inactive" or "Shadowed"
+ ass:append(self:get_font_color("comment"))
+
+ if comment ~= "" then
+ ass:append(comment .. "\\h")
+ end
+
+ ass:append(key .. "\\h(" .. why_inactive .. ")" .. "\\h" .. cmd)
+ return ass.text
+ end
+
+ if comment ~= "" then
+ ass:append(self:get_font_color("default"))
+ ass:append(comment .. "\\h")
+ end
+
+ ass:append(self:get_font_color("accent"))
+ ass:append(key)
+ ass:append(self:get_font_color("comment"))
+ ass:append(" " .. cmd)
+ return ass.text
+end
+
+function command_palette_get_line(self, _, v)
+ local ass = assdraw.ass_new()
+
+ if v.key == "" then
+ ass:append(self:get_font_color("default"))
+ ass:append(self:ass_escape(v.name or ""))
+ else
+ ass:append(self:get_font_color("default"))
+ ass:append(self:ass_escape(v.name or "") .. "\\h")
+
+ ass:append(self:get_font_color("accent"))
+ ass:append(self:ass_escape("(" .. v.key .. ")"))
+ end
+
+ return ass.text
+end
+
+local function format_flags(track)
+ local flags = ""
+
+ for _, flag in ipairs({
+ "default",
+ "forced",
+ "dependent",
+ "visual-impaired",
+ "hearing-impaired",
+ "image",
+ "external",
+ }) do
+ if track[flag] then
+ flags = flags .. flag .. " "
+ end
+ end
+
+ if flags == "" then
+ return ""
+ end
+
+ return " [" .. flags:sub(1, -2) .. "]"
+end
+
+local function fix_codec(value)
+ if contains(value, "hdmv_pgs_subtitle") then
+ value = replace(value, "hdmv_pgs_subtitle", "pgs")
+ end
+
+ return value:upper()
+end
+
+local function get_language(lng)
+ if lng == nil or lng == "" then
+ return lng
+ end
+
+ if lng == "ara" then
+ lng = "Arabic"
+ end
+ if lng == "ben" then
+ lng = "Bangla"
+ end
+ if lng == "bng" then
+ lng = "Bangla"
+ end
+ if lng == "chi" then
+ lng = "Chinese"
+ end
+ if lng == "zho" then
+ lng = "Chinese"
+ end
+ if lng == "eng" then
+ lng = "English"
+ end
+ if lng == "fre" then
+ lng = "French"
+ end
+ if lng == "fra" then
+ lng = "French"
+ end
+ if lng == "ger" then
+ lng = "German"
+ end
+ if lng == "deu" then
+ lng = "German"
+ end
+ if lng == "hin" then
+ lng = "Hindi"
+ end
+ if lng == "ita" then
+ lng = "Italian"
+ end
+ if lng == "jpn" then
+ lng = "Japanese"
+ end
+ if lng == "kor" then
+ lng = "Korean"
+ end
+ if lng == "msa" then
+ lng = "Malay"
+ end
+ if lng == "por" then
+ lng = "Portuguese"
+ end
+ if lng == "pan" then
+ lng = "Punjabi"
+ end
+ if lng == "rus" then
+ lng = "Russian"
+ end
+ if lng == "spa" then
+ lng = "Spanish"
+ end
+ if lng == "und" then
+ lng = "Undetermined"
+ end
+
+ return lng
+end
+
+local function format_track(track)
+ local lng = get_language(track.lang)
+ return (track.selected and "●" or "○")
+ .. (
+ (lng and lng .. " " or "")
+ .. fix_codec(track.codec and track.codec .. " " or "")
+ .. (track["demux-w"] and track["demux-w"] .. "x" .. track["demux-h"] .. " " or "")
+ .. (track["demux-fps"] and not track.image and string.format("%.4f", track["demux-fps"]):gsub("%.?0*$", "") .. " fps " or "")
+ .. (track["demux-channel-count"] and track["demux-channel-count"] .. "ch " or "")
+ .. (track["codec-profile"] and track.type == "audio" and track["codec-profile"] .. " " or "")
+ .. (track["demux-samplerate"] and track["demux-samplerate"] / 1000 .. " kHz " or "")
+ .. (track["demux-bitrate"] and string.format("%.0f", track["demux-bitrate"] / 1000) .. " kbps " or "")
+ .. (track["hls-bitrate"] and string.format("%.0f", track["hls-bitrate"] / 1000) .. " HLS kbps " or "")
+ ):sub(1, -2)
+ .. format_flags(track)
+ .. (track.title and " " .. track.title or "")
+end
+
+local function select(conf)
+ for k, v in ipairs(conf.items) do
+ table.insert(menu_content.list, { index = k, content = v })
+ end
+
+ if conf.default_item then
+ menu_content.current_i = conf.default_item
+ end
+
+ function menu:submit(value)
+ conf.submit(value)
+ end
+end
+
+local function select_track(property, type, error)
+ local tracks = {}
+ local items = {}
+ local default_item
+ local track_id = mp.get_property_native(property)
+
+ for _, track in ipairs(mp.get_property_native("track-list")) do
+ if track.type == type then
+ tracks[#tracks + 1] = track
+ items[#items + 1] = format_track(track)
+
+ if track.id == track_id then
+ default_item = #items
+ end
+ end
+ end
+
+ if #items == 0 then
+ mp.commandv("show-text", error)
+ return
+ end
+
+ select({
+ items = items,
+ default_item = default_item,
+ submit = function(tbl)
+ mp.command("set " .. property .. " " .. (tracks[tbl.index].selected and "no" or tracks[tbl.index].id))
+ end,
+ })
+end
+
+function hide_osc()
+ if is_empty(mp.get_property("path")) and not is_older_than_v0_36 then
+ osc_visibility = mp.get_property_native("user-data/osc/visibility")
+
+ if osc_visibility == "auto" or osc_visibility == "always" then
+ mp.command("script-message osc-visibility never no_osd")
+ end
+ end
+
+ if uosc_available then
+ local disable_elements =
+ "window_border, top_bar, timeline, controls, volume, idle_indicator, audio_indicator, buffering_indicator, pause_indicator"
+ mp.commandv("script-message-to", "uosc", "disable-elements", mp.get_script_name(), disable_elements)
+ end
+end
+
+mp.register_script_message("show-command-palette", function(name)
+ if dpiScale == 0 then
+ dpiScale = mp.get_property_native("display-hidpi-scale", 1)
+ end
+
+ o.font_size = originalFontSize * dpiScale
+
+ menu_content.list = {}
+ menu_content.current_i = 1
+ menu.search_heading = name
+ menu.filter_by_fields = { "content" }
+ em.get_line = original_get_line_func
+
+ if name == "Command Palette" then
+ local menu_items = {}
+ local bindings = utils.parse_json(mp.get_property("input-bindings"))
+
+ local items = {
+ "Playlist",
+ "Tracks",
+ "Video Tracks",
+ "Audio Tracks",
+ "Subtitle Tracks",
+ "Secondary Subtitle",
+ "Subtitle Line",
+ "Chapters",
+ "Profiles",
+ "Bindings",
+ "Commands",
+ "Properties",
+ "Options",
+ "Audio Devices",
+ "Blu-ray Titles",
+ "Stream Quality",
+ "Aspect Ratio",
+ "Command Palette",
+ "Recent Files",
+ }
+
+ for _, item in ipairs(items) do
+ local found = false
+
+ for _, binding in ipairs(bindings) do
+ if
+ contains(binding.cmd, "show-command-palette")
+ and (contains(binding.cmd, '"' .. item .. '"') or contains(binding.cmd, "'" .. item .. "'"))
+ then
+ table.insert(menu_items, { name = item, key = binding.key, cmd = binding.cmd })
+ found = true
+ break
+ end
+ end
+
+ if not found then
+ local cmd = "script-message-to command_palette show-command-palette '" .. item .. "'"
+ table.insert(menu_items, { name = item, key = "", cmd = cmd })
+ end
+ end
+
+ menu_content.list = menu_items
+
+ function menu:submit(tbl)
+ mp.command(tbl.cmd)
+ end
+
+ menu.filter_by_fields = { "name", "key" }
+ em.get_line = command_palette_get_line
+ elseif name == "Bindings" then
+ local bindings = utils.parse_json(mp.get_property("input-bindings"))
+
+ for _, v in ipairs(bindings) do
+ v.key = "(" .. v.key .. ")"
+
+ if not is_empty(v.comment) then
+ if contains(v.comment, "custom-menu: ") then
+ v.comment = replace(v.comment, "custom-menu: ", "")
+ end
+
+ if contains(v.comment, "menu: ") then
+ v.comment = replace(v.comment, "menu: ", "")
+ end
+
+ v.comment = first_to_upper(v.comment)
+ end
+ end
+
+ for _, v in ipairs(bindings) do
+ for _, v2 in ipairs(bindings) do
+ if v.key == v2.key and v.priority < v2.priority then
+ v.priority = -2
+ break
+ end
+ end
+ end
+
+ table.sort(bindings, function(i, j)
+ return i.priority > j.priority
+ end)
+
+ menu_content.list = bindings
+
+ function menu:submit(tbl)
+ mp.command(tbl.cmd)
+ end
+
+ menu.filter_by_fields = { "cmd", "key", "comment" }
+ em.get_line = binding_get_line
+ elseif name == "Chapters" then
+ local default_index = mp.get_property_native("chapter")
+
+ if not default_index then
+ mp.commandv("show-text", "Chapter: (unavailable)")
+ return
+ end
+
+ local duration = mp.get_property_native("duration", math.huge)
+
+ for i, chapter in ipairs(mp.get_property_native("chapter-list")) do
+ table.insert(
+ menu_content.list,
+ { index = i, content = format_time(chapter.time, duration) .. " " .. chapter.title }
+ )
+ end
+
+ menu_content.current_i = default_index + 1
+
+ function menu:submit(tbl)
+ mp.set_property_number("chapter", tbl.index - 1)
+ end
+ elseif name == "Playlist" then
+ local count = mp.get_property_number("playlist-count")
+ if count == 0 then
+ return
+ end
+
+ for i = 0, (count - 1) do
+ local text = mp.get_property("playlist/" .. i .. "/title")
+
+ if text == nil then
+ text = file_name(mp.get_property("playlist/" .. i .. "/filename"))
+ end
+
+ table.insert(menu_content.list, { index = i + 1, content = text })
+ end
+
+ menu_content.current_i = mp.get_property_number("playlist-pos") + 1
+
+ function menu:submit(tbl)
+ mp.set_property_number("playlist-pos", tbl.index - 1)
+ end
+ elseif name == "Commands" then
+ local commands = utils.parse_json(mp.get_property("command-list"))
+
+ for k, v in ipairs(commands) do
+ local text = v.name
+
+ for _, arg in ipairs(v.args) do
+ if arg.optional then
+ text = text .. " [<" .. arg.name .. ">]"
+ else
+ text = text .. " <" .. arg.name .. ">"
+ end
+ end
+
+ table.insert(menu_content.list, { index = k, content = text })
+ end
+
+ function menu:submit(tbl)
+ print(tbl.content)
+ local cmd = string.match(tbl.content, "%S+")
+ mp.commandv("script-message-to", "console", "type", cmd .. " ")
+ end
+ elseif name == "Properties" then
+ local properties = split(mp.get_property("property-list"), ",")
+
+ for k, v in ipairs(properties) do
+ table.insert(menu_content.list, { index = k, content = v })
+ end
+
+ function menu:submit(tbl)
+ mp.commandv("script-message-to", "console", "type", "print-text ${" .. tbl.content .. "}")
+ end
+ elseif name == "Options" then
+ local options = split(mp.get_property("options"), ",")
+
+ for k, v in ipairs(options) do
+ local type = mp.get_property_osd("option-info/" .. v .. "/type", "")
+ local default = mp.get_property_osd("option-info/" .. v .. "/default-value", "")
+ v = v .. " (type: " .. type .. ", default: " .. default .. ")"
+ table.insert(menu_content.list, { index = k, content = v })
+ end
+
+ function menu:submit(tbl)
+ print(tbl.content)
+ local prop = string.match(tbl.content, "%S+")
+ mp.commandv("script-message-to", "console", "type", "set " .. prop .. " ")
+ end
+ elseif name == "Profiles" then
+ local profiles = utils.parse_json(mp.get_property("profile-list"))
+ local ignore_list = { "builtin-pseudo-gui", "encoding", "libmpv", "pseudo-gui", "default" }
+
+ for k, v in ipairs(profiles) do
+ if not list_contains(ignore_list, v.name) then
+ table.insert(menu_content.list, { index = k, content = v.name })
+ end
+ end
+
+ function menu:submit(tbl)
+ mp.command("show-text " .. tbl.content)
+ mp.command("apply-profile " .. tbl.content)
+ end
+ elseif name == "Audio Devices" then
+ local devices = utils.parse_json(mp.get_property("audio-device-list"))
+ local current_name = mp.get_property("audio-device")
+
+ for k, v in ipairs(devices) do
+ table.insert(menu_content.list, { index = k, name = v.name, content = v.description })
+
+ if v.name == current_name then
+ menu_content.current_i = k
+ end
+ end
+
+ function menu:submit(tbl)
+ mp.commandv("set", "audio-device", tbl.name)
+ mp.commandv("show-text", "audio-device: " .. tbl.content)
+ end
+ elseif name == "Aspect Ratio" then
+ local current_ar = mp.get_property_number("video-aspect-override")
+
+ for k, v in ipairs(split(o.aspect_ratios, ",")) do
+ local display_name = v
+
+ if display_name == "0" then
+ display_name = "0 (square pixels)"
+ end
+ if display_name == "-1" then
+ display_name = "-1 (original)"
+ end
+
+ table.insert(menu_content.list, { index = k, content = display_name, value = v })
+
+ local w, h = string.match(v, "^([0-9.]+):([0-9.]+)$")
+
+ if w and h then
+ local current_ar_truncated = tonumber(string.format("%.3f", current_ar))
+ local ar_truncated = tonumber(string.format("%.3f", w / h))
+
+ if current_ar_truncated == ar_truncated then
+ menu_content.current_i = k
+ end
+ elseif v == tostring(current_ar) then
+ menu_content.current_i = k
+ end
+ end
+
+ function menu:submit(tbl)
+ mp.command("set video-aspect-override " .. tbl.value)
+ end
+ elseif name == "Stream Quality" then
+ local ytdl_format = mp.get_property_native("ytdl-format")
+
+ for k, v in ipairs(split(o.stream_quality_options, ",")) do
+ local format = "bestvideo[height<=?" .. v .. "]+bestaudio/best[height<=?" .. v .. "]"
+ table.insert(menu_content.list, { index = k, content = v .. "p", value = format })
+
+ if format == ytdl_format then
+ menu_content.current_i = k
+ end
+ end
+
+ function menu:submit(tbl)
+ mp.set_property("ytdl-format", tbl.value)
+ mp.commandv("show-text", "Stream Quality: " .. tbl.content)
+
+ local duration = mp.get_property_native("duration")
+ local time_pos = mp.get_property("time-pos")
+
+ mp.command("playlist-play-index current")
+
+ if duration and duration > 0 then
+ local function seeker()
+ mp.commandv("seek", time_pos, "absolute")
+ mp.unregister_event(seeker)
+ end
+
+ mp.register_event("file-loaded", seeker)
+ end
+ end
+ elseif name == "Tracks" then
+ local tracks = {}
+
+ for i, track in ipairs(mp.get_property_native("track-list")) do
+ local type = track.image and "I" or track.type
+
+ if type == "video" then
+ type = "V"
+ end
+ if type == "audio" then
+ type = "A"
+ end
+ if type == "sub" then
+ type = "S"
+ end
+
+ tracks[i] = type .. ": " .. format_track(track)
+ end
+
+ if #tracks == 0 then
+ mp.commandv("show-text", "No available tracks")
+ return
+ end
+
+ select({
+ items = tracks,
+ submit = function(tbl)
+ local track = mp.get_property_native("track-list/" .. tbl.index - 1)
+
+ if track then
+ mp.command("set " .. track.type .. " " .. (track.selected and "no" or track.id))
+ end
+ end,
+ })
+ elseif name == "Audio Tracks" then
+ if o.use_mediainfo then
+ local mi = get_media_info()
+ if mi == nil then
+ return
+ end
+ local tracks = split(mi .. "\nA: None", "\n")
+ local id = 0
+
+ for _, v in ipairs(tracks) do
+ if starts_with(v, "A: ") then
+ id = id + 1
+ table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
+ end
+ end
+
+ menu_content.current_i = mp.get_property_number("aid") or id
+
+ function menu:submit(tbl)
+ mp.command("set aid " .. ((tbl.index == id) and "no" or tbl.index))
+ end
+ else
+ select_track("aid", "audio", "No available audio tracks")
+ end
+ elseif name == "Subtitle Tracks" then
+ if o.use_mediainfo then
+ local mi = get_media_info()
+ if mi == nil then
+ return
+ end
+ local tracks = split(mi .. "\nS: None", "\n")
+ local id = 0
+
+ for _, v in ipairs(tracks) do
+ if starts_with(v, "S: ") then
+ id = id + 1
+ table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
+ end
+ end
+
+ menu_content.current_i = mp.get_property_number("sid") or id
+
+ function menu:submit(tbl)
+ mp.command("set sid " .. ((tbl.index == id) and "no" or tbl.index))
+ end
+ else
+ select_track("sid", "sub", "No available subtitle tracks")
+ end
+ elseif name == "Secondary Subtitle" then
+ select_track("secondary-sid", "sub", "No available subtitle tracks")
+ elseif name == "Recent Files" then
+ local frontend = mp.get_property_native("user-data/frontend/name")
+
+ if frontend == "mpv.net" then
+ mp.command("script-message show-recent-in-command-palette")
+ else
+ mp.command("script-message open-recent-menu command-palette")
+ end
+
+ return
+ elseif name == "Video Tracks" then
+ if o.use_mediainfo then
+ local mi = get_media_info()
+ if mi == nil then
+ return
+ end
+ local tracks = split(mi .. "\nV: None", "\n")
+ local id = 0
+
+ for _, v in ipairs(tracks) do
+ if starts_with(v, "V: ") then
+ id = id + 1
+ table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
+ end
+ end
+
+ menu_content.current_i = mp.get_property_number("vid") or id
+
+ function menu:submit(tbl)
+ mp.command("set vid " .. ((tbl.index == id) and "no" or tbl.index))
+ end
+ else
+ select_track("vid", "video", "No available video tracks")
+ end
+ elseif name == "Blu-ray Titles" then
+ if #BluRayTitles == 0 then
+ return
+ end
+
+ local items = {}
+
+ for k, v in ipairs(BluRayTitles) do
+ table.insert(items, "Title " .. k .. " " .. v)
+ end
+
+ select({
+ items = items,
+ submit = function(tbl)
+ mp.commandv("loadfile", "bd://" .. (tbl.index - 1))
+ end,
+ })
+ elseif name == "Subtitle Line" then
+ local sub = mp.get_property_native("current-tracks/sub")
+
+ if sub == nil then
+ mp.commandv("show-text", "No subtitle is loaded")
+ return
+ end
+
+ if sub.external and sub["external-filename"]:find("^edl://") then
+ sub["external-filename"] = sub["external-filename"]:match("https?://.*") or sub["external-filename"]
+ end
+
+ local r = mp.command_native({
+ name = "subprocess",
+ capture_stdout = true,
+ args = sub.external and {
+ "ffmpeg",
+ "-loglevel",
+ "error",
+ "-i",
+ sub["external-filename"],
+ "-f",
+ "lrc",
+ "-map_metadata",
+ "-1",
+ "-fflags",
+ "+bitexact",
+ "-",
+ } or {
+ "ffmpeg",
+ "-loglevel",
+ "error",
+ "-i",
+ mp.get_property("path"),
+ "-map",
+ "s:" .. sub["id"] - 1,
+ "-f",
+ "lrc",
+ "-map_metadata",
+ "-1",
+ "-fflags",
+ "+bitexact",
+ "-",
+ },
+ })
+
+ if r.error_string == "init" then
+ mp.commandv("show-text", "Failed to extract subtitles: ffmpeg not found")
+ return
+ elseif r.status ~= 0 then
+ mp.commandv("show-text", "Failed to extract subtitles")
+ return
+ end
+
+ local sub_lines = {}
+ local sub_times = {}
+ local default_item
+ local delay = mp.get_property_native("sub-delay")
+ local time_pos = mp.get_property_native("time-pos") - delay
+ local duration = mp.get_property_native("duration", math.huge)
+
+ -- Strip HTML and ASS tags.
+ for line in r.stdout:gsub("<.->", ""):gsub("{\\.-}", ""):gmatch("[^\n]+") do
+ -- ffmpeg outputs LRCs with minutes > 60 instead of adding hours.
+ sub_times[#sub_times + 1] = line:match("%d+") * 60 + line:match(":([%d%.]*)")
+ sub_lines[#sub_lines + 1] = format_time(sub_times[#sub_times], duration) .. " " .. line:gsub(".*]", "", 1)
+
+ if sub_times[#sub_times] <= time_pos then
+ default_item = #sub_times
+ end
+ end
+
+ select({
+ items = sub_lines,
+ default_item = default_item,
+ submit = function(tbl)
+ -- Add an offset to seek to the correct line while paused without a video track.
+ if mp.get_property_native("current-tracks/video/image") ~= false then
+ delay = delay + 0.1
+ end
+
+ mp.commandv("seek", sub_times[tbl.index] + delay, "absolute")
+ end,
+ })
+ else
+ if name == nil then
+ msg.error("Unknown mode")
+ else
+ msg.error("Unknown mode: " .. name)
+ end
+
+ return
+ end
+
+ hide_osc()
+ menu:init(menu_content)
+end)
+
+mp.register_script_message("uosc-version", function(version)
+ local major, minor = version:match("^(%d+)%.(%d+)")
+ if major and minor and tonumber(major) >= 5 and tonumber(minor) >= 0 then
+ uosc_available = true
+ end
+end)
+
+mp.register_script_message("show-command-palette-json", function(json)
+ if dpiScale == 0 then
+ dpiScale = mp.get_property_native("display-hidpi-scale", 1)
+ end
+
+ o.font_size = originalFontSize * dpiScale
+
+ local menu_data = utils.parse_json(json)
+ menu_content.list = {}
+ menu_content.current_i = 1
+ menu.search_heading = menu_data.title
+ menu.filter_by_fields = { "content", "hint", "value_hint" }
+ em.get_line = original_get_line_func
+
+ for k, v in ipairs(menu_data.items) do
+ local values = v.value
+
+ if type(values) == "string" then
+ values = { values }
+ end
+
+ table.insert(menu_content.list, {
+ index = k,
+ content = v.title,
+ hint = v.hint,
+ values = values,
+ value_hint = table.concat(values, " "),
+ })
+
+ if menu_data.selected_index then
+ menu_content.current_i = menu_data.selected_index
+ end
+ end
+
+ function menu:submit(tbl)
+ mp.command_native(tbl.values)
+ end
+
+ hide_osc()
+ menu:init(menu_content)
+end)
diff --git a/ar/.config/mpv/scripts/cycle-video-rotate.lua b/ar/.config/mpv/scripts/cycle-video-rotate.lua
new file mode 100644
index 0000000..e7be9e2
--- /dev/null
+++ b/ar/.config/mpv/scripts/cycle-video-rotate.lua
@@ -0,0 +1,36 @@
+-- -----------------------------------------------------------
+--
+-- CYCLE-VIDEO-ROTATE.LUA
+-- Version: 1.0
+-- Author: VideoPlayerCode
+-- URL: https://github.com/VideoPlayerCode/mpv-tools
+--
+-- Description:
+--
+-- Allows you to perform video rotation which perfectly
+-- cycles through all 360 degrees without any glitches.
+--
+-- -----------------------------------------------------------
+
+function cycle_video_rotate(amt)
+ -- Ensure that amount is a base 10 integer.
+ amt = tonumber(amt, 10)
+ if amt == nil then
+ mp.osd_message("Rotate: Invalid rotation amount")
+ return nil -- abort
+ end
+
+ -- Calculate what the next rotation value should be,
+ -- and wrap value to correct range (0 (aka 360) to 359).
+ local newrotate = mp.get_property_number("video-rotate")
+ newrotate = (newrotate + amt) % 360
+
+ -- Change rotation and tell the user.
+ mp.set_property_number("video-rotate", newrotate)
+ mp.osd_message("Rotate: " .. newrotate)
+end
+
+-- Bind this via input.conf. Example:
+-- Alt+LEFT script-message Cycle_Video_Rotate -90
+-- Alt+RIGHT script-message Cycle_Video_Rotate 90
+mp.register_script_message("cycle_video_rotate", cycle_video_rotate)
diff --git a/ar/.config/mpv/scripts/delete_current_file.lua b/ar/.config/mpv/scripts/delete_current_file.lua
new file mode 100644
index 0000000..c30426b
--- /dev/null
+++ b/ar/.config/mpv/scripts/delete_current_file.lua
@@ -0,0 +1,168 @@
+
+--[[
+
+ https://github.com/stax76/mpv-scripts
+
+ This script instantly deletes the file that is currently playing
+ via keyboard shortcut, the file is moved to the recycle bin and
+ removed from the playlist.
+
+ On Linux the app trash-cli must be installed first.
+
+ Usage:
+ Add bindings to input.conf:
+
+ # delete directly
+ KP0 script-message-to delete_current_file delete-file
+
+ # delete with confirmation
+ KP0 script-message-to delete_current_file delete-file KP1 "Press 1 to delete file"
+
+ Press KP0 to initiate the delete operation,
+ the script will ask to confirm by pressing KP1.
+ You may customize the the init and confirm key and the confirm message.
+ Confirm key and confirm message are optional.
+
+ Similar scripts:
+ https://github.com/zenyd/mpv-scripts#delete-file
+
+]]--
+
+key_bindings = {}
+
+function file_exists(name)
+ if not name or name == '' then
+ return false
+ end
+
+ local f = io.open(name, "r")
+
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+function is_protocol(path)
+ return type(path) == 'string' and (path:match('^%a[%a%d_-]+://'))
+end
+
+function delete_file(path)
+ local is_windows = package.config:sub(1,1) == "\\"
+
+ if is_protocol(path) or not file_exists(path) then
+ return
+ end
+
+ if is_windows then
+ local ps_code = [[
+ Add-Type -AssemblyName Microsoft.VisualBasic
+ [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('__path__', 'OnlyErrorDialogs', 'SendToRecycleBin')
+ ]]
+
+ local escaped_path = string.gsub(path, "'", "''")
+ escaped_path = string.gsub(escaped_path, "’", "’’")
+ escaped_path = string.gsub(escaped_path, "%%", "%%%%")
+ ps_code = string.gsub(ps_code, "__path__", escaped_path)
+
+ mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ detach = true,
+ args = { 'powershell', '-NoProfile', '-Command', ps_code },
+ })
+ else
+ mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ args = { 'trash', path },
+ })
+ end
+end
+
+function remove_current_file()
+ local count = mp.get_property_number("playlist-count")
+ local pos = mp.get_property_number("playlist-pos")
+ local new_pos = 0
+
+ if pos == count - 1 then
+ new_pos = pos - 1
+ else
+ new_pos = pos + 1
+ end
+
+ mp.set_property_number("playlist-pos", new_pos)
+
+ if pos > -1 then
+ mp.command("playlist-remove " .. pos)
+ end
+end
+
+function handle_confirm_key()
+ local path = mp.get_property("path")
+
+ if file_to_delete == path then
+ mp.commandv("show-text", "")
+ delete_file(file_to_delete)
+ remove_current_file()
+ remove_bindings()
+ file_to_delete = ""
+ end
+end
+
+function cleanup()
+ remove_bindings()
+ file_to_delete = ""
+ mp.commandv("show-text", "")
+end
+
+function get_bindings()
+ return {
+ { confirm_key, handle_confirm_key },
+ }
+end
+
+function add_bindings()
+ if #key_bindings > 0 then
+ return
+ end
+
+ local script_name = mp.get_script_name()
+
+ for _, bind in ipairs(get_bindings()) do
+ local name = script_name .. "_key_" .. (#key_bindings + 1)
+ key_bindings[#key_bindings + 1] = name
+ mp.add_forced_key_binding(bind[1], name, bind[2])
+ end
+end
+
+function remove_bindings()
+ if #key_bindings == 0 then
+ return
+ end
+
+ for _, name in ipairs(key_bindings) do
+ mp.remove_key_binding(name)
+ end
+
+ key_bindings = {}
+end
+
+function client_message(event)
+ local path = mp.get_property("path")
+
+ if event.args[1] == "delete-file" and #event.args == 1 then
+ delete_file(path)
+ remove_current_file()
+ elseif event.args[1] == "delete-file" and #event.args == 3 and #key_bindings == 0 then
+ confirm_key = event.args[2]
+ mp.add_timeout(10, cleanup)
+ add_bindings()
+ file_to_delete = path
+ mp.commandv("show-text", event.args[3], "10000")
+ end
+end
+
+mp.register_event("client-message", client_message)
diff --git a/ar/.config/mpv/scripts/fuzzydir.lua b/ar/.config/mpv/scripts/fuzzydir.lua
new file mode 100644
index 0000000..22119da
--- /dev/null
+++ b/ar/.config/mpv/scripts/fuzzydir.lua
@@ -0,0 +1,278 @@
+--[[
+ fuzzydir / by sibwaf / https://github.com/sibwaf/mpv-scripts
+
+ Allows using "**" wildcards in sub-file-paths and audio-file-paths
+ so you don't have to specify all the possible directory names.
+
+ Basically, allows you to do this and never have the need to edit any paths ever again:
+ audio-file-paths = **
+ sub-file-paths = **
+
+ MIT license - do whatever you want, but I'm not responsible for any possible problems.
+ Please keep the URL to the original repository. Thanks!
+]]
+
+--[[
+ Configuration:
+
+ # max_search_depth
+
+ Determines the max depth of recursive search, should be >= 1
+
+ Examples for "sub-file-paths = **":
+ "max_search_depth = 1" => mpv will be able to find [xyz.ass, subs/xyz.ass]
+ "max_search_depth = 2" => mpv will be able to find [xyz.ass, subs/xyz.ass, subs/moresubs/xyz.ass]
+
+ Please be careful when setting this value too high as it can result in awful performance or even stack overflow
+
+
+ # discovery_threshold
+
+ fuzzydir will skip paths which contain more than discovery_threshold directories in them
+
+ This is done to keep at least some garbage from getting into *-file-paths properties in case of big collections:
+ - dir1 <- will be ignored on opening video.mp4 as it's probably unrelated to the file
+ - ...
+ - dir999 <- will be ignored
+ - video.mp4
+
+ Use 0 to disable this behavior completely
+
+
+ # use_powershell
+
+ fuzzydir will use PowerShell to traverse directories when it's available
+
+ Can be faster in some cases, but can also be significantly slower
+]]
+
+local max_search_depth = 3
+local discovery_threshold = 10
+local use_powershell = false
+
+----------
+
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+
+local default_audio_paths = mp.get_property_native("options/audio-file-paths")
+local default_sub_paths = mp.get_property_native("options/sub-file-paths")
+
+function foreach(list, action)
+ for _, item in pairs(list) do
+ action(item)
+ end
+end
+
+function starts_with(str, prefix)
+ return string.sub(str, 1, string.len(prefix)) == prefix
+end
+
+function ends_with(str, suffix)
+ return suffix == "" or string.sub(str, -string.len(suffix)) == suffix
+end
+
+function add_all(to, from)
+ for index, element in pairs(from) do
+ table.insert(to, element)
+ end
+end
+
+function contains(t, e)
+ for index, element in pairs(t) do
+ if element == e then
+ return true
+ end
+ end
+ return false
+end
+
+function normalize(path)
+ if path == "." then
+ return ""
+ end
+
+ if starts_with(path, "./") or starts_with(path, ".\\") then
+ path = string.sub(path, 3, -1)
+ end
+ if ends_with(path, "/") or ends_with(path, "\\") then
+ path = string.sub(path, 1, -2)
+ end
+
+ return path
+end
+
+function call_command(command)
+ local command_string = ""
+ for _, part in pairs(command) do
+ command_string = command_string .. part .. " "
+ end
+
+ msg.trace("Calling external command:", command_string)
+
+ local process = mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ args = command,
+ })
+
+ if process.status ~= 0 then
+ msg.verbose("External command failed with status " .. process.status .. ": " .. command_string)
+ if process.stderr ~= "" then
+ msg.debug(process.stderr)
+ end
+
+ return nil
+ end
+
+ local result = {}
+ for line in string.gmatch(process.stdout, "([^\r\n]+)") do
+ table.insert(result, line)
+ end
+ return result
+end
+
+-- Platform-dependent optimization
+
+local powershell_version = nil
+if use_powershell then
+ powershell_version = call_command({
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ "$Host.Version.Major",
+ })
+end
+if powershell_version ~= nil then
+ powershell_version = tonumber(powershell_version[1])
+end
+if powershell_version == nil then
+ powershell_version = -1
+end
+msg.debug("PowerShell version", powershell_version)
+
+function fast_readdir(path)
+ if powershell_version >= 3 then
+ msg.trace("Scanning", path, "with PowerShell")
+ result = call_command({
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ [[
+ $dirs = Get-ChildItem -LiteralPath ]] .. string.format("%q", path) .. [[ -Directory
+ foreach($dir in $dirs) {
+ $u8clip = [System.Text.Encoding]::UTF8.GetBytes($dir.Name)
+ [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
+ Write-Host ""
+ } ]],
+ })
+ msg.trace("Finished scanning", path, "with PowerShell")
+ return result
+ end
+
+ msg.trace("Scanning", path, "with default readdir")
+ result = utils.readdir(path, "dirs")
+ msg.trace("Finished scanning", path, "with default readdir")
+ return result
+end
+
+-- Platform-dependent optimization end
+
+function traverse(search_path, current_path, level, cache)
+ local full_path = utils.join_path(search_path, current_path)
+
+ if level > max_search_depth then
+ msg.trace("Traversed too deep, skipping scan for", full_path)
+ return {}
+ end
+
+ if cache[full_path] ~= nil then
+ msg.trace("Returning from cache for", full_path)
+ return cache[full_path]
+ end
+
+ local result = {}
+
+ local discovered_paths = fast_readdir(full_path)
+ if discovered_paths == nil then
+ -- noop
+ msg.debug("Unable to scan " .. full_path .. ", skipping")
+ elseif discovery_threshold > 0 and #discovered_paths > discovery_threshold then
+ -- noop
+ msg.debug("Too many directories in " .. full_path .. ", skipping")
+ else
+ for _, discovered_path in pairs(discovered_paths) do
+ local new_path = utils.join_path(current_path, discovered_path)
+
+ table.insert(result, new_path)
+ add_all(result, traverse(search_path, new_path, level + 1, cache))
+ end
+ end
+
+ cache[full_path] = result
+
+ return result
+end
+
+function explode(raw_paths, search_path, cache)
+ local result = {}
+ for _, raw_path in pairs(raw_paths) do
+ local parent, leftover = utils.split_path(raw_path)
+ if leftover == "**" then
+ msg.trace("Expanding wildcard for", raw_path)
+ table.insert(result, parent)
+ add_all(result, traverse(search_path, parent, 1, cache))
+ else
+ msg.trace("Path", raw_path, "doesn't have a wildcard, keeping as-is")
+ table.insert(result, raw_path)
+ end
+ end
+
+ local normalized = {}
+ for index, path in pairs(result) do
+ local normalized_path = normalize(path)
+ if not contains(normalized, normalized_path) and normalized_path ~= "" then
+ table.insert(normalized, normalized_path)
+ end
+ end
+
+ return normalized
+end
+
+function explode_all()
+ msg.debug("max_search_depth = " .. max_search_depth .. ", discovery_threshold = " .. discovery_threshold)
+
+ local video_path = mp.get_property("path")
+ local search_path, _ = utils.split_path(video_path)
+ msg.debug("search_path = " .. search_path)
+
+ local cache = {}
+
+ foreach(default_audio_paths, function(it)
+ msg.debug("audio-file-paths:", it)
+ end)
+ local audio_paths = explode(default_audio_paths, search_path, cache)
+ foreach(audio_paths, function(it)
+ msg.debug("Adding to audio-file-paths:", it)
+ end)
+ mp.set_property_native("options/audio-file-paths", audio_paths)
+
+ msg.verbose("Done expanding audio-file-paths")
+
+ foreach(default_sub_paths, function(it)
+ msg.debug("sub-file-paths:", it)
+ end)
+ local sub_paths = explode(default_sub_paths, search_path, cache)
+ foreach(sub_paths, function(it)
+ msg.debug("Adding to sub-file-paths:", it)
+ end)
+ mp.set_property_native("options/sub-file-paths", sub_paths)
+
+ msg.verbose("Done expanding sub-file-paths")
+
+ msg.debug("Done expanding paths")
+end
+
+mp.add_hook("on_load", 50, explode_all)
diff --git a/ar/.config/mpv/scripts/gallery-thumbgen.lua b/ar/.config/mpv/scripts/gallery-thumbgen.lua
new file mode 100644
index 0000000..dc0db1a
--- /dev/null
+++ b/ar/.config/mpv/scripts/gallery-thumbgen.lua
@@ -0,0 +1,342 @@
+--[[
+mpv-gallery-view | https://github.com/occivink/mpv-gallery-view
+
+This mpv script implements a worker for generating gallery thumbnails.
+It is meant to be used by other scripts.
+Multiple copies of this script can be loaded by mpv.
+
+File placement: inside scripts directory
+Settings: script-opts/gallery_worker.conf
+]]
+
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+
+local jobs_queue = {} -- queue of thumbnail jobs
+local failed = {} -- list of failed output paths, to avoid redoing them
+local script_id = mp.get_script_name() .. utils.getpid()
+
+local opts = {
+ ytdl_exclude = "",
+};
+(require("mp.options")).read_options(opts, "gallery_worker")
+
+local ytdl = {
+ path = "youtube-dl",
+ searched = false,
+ blacklisted = {}, -- Add patterns of URLs you want blacklisted from youtube-dl,
+ -- see gallery_worker.conf or ytdl_hook-exclude in the mpv manpage for more info
+}
+
+function append_table(lhs, rhs)
+ for i = 1, #rhs do
+ lhs[#lhs + 1] = rhs[i]
+ end
+ return lhs
+end
+
+local function file_exists(path)
+ local info = utils.file_info(path)
+ return info ~= nil and info.is_file
+end
+
+local video_extensions = { "mkv", "webm", "mp4", "avi", "wmv" }
+
+function is_video(input_path)
+ local extension = string.match(input_path, "%.([^.]+)$")
+ if extension then
+ extension = string.lower(extension)
+ for _, ext in ipairs(video_extensions) do
+ if extension == ext then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+function is_blacklisted(url)
+ if opts.ytdl_exclude == "" then
+ return false
+ end
+ if #ytdl.blacklisted == 0 then
+ local joined = opts.ytdl_exclude
+ while joined:match("%|?[^|]+") do
+ local _, e, substring = joined:find("%|?([^|]+)")
+ table.insert(ytdl.blacklisted, substring)
+ joined = joined:sub(e + 1)
+ end
+ end
+ if #ytdl.blacklisted > 0 then
+ url = url:match("https?://(.+)")
+ for _, exclude in ipairs(ytdl.blacklisted) do
+ if url:match(exclude) then
+ msg.verbose("URL matches excluded substring. Skipping.")
+ return true
+ end
+ end
+ end
+ return false
+end
+
+function ytdl_thumbnail_url(input_path)
+ local function exec(args)
+ local ret = utils.subprocess({ args = args, cancellable = false })
+ return ret.status, ret.stdout, ret
+ end
+ local function first_non_nil(x, ...)
+ if x ~= nil then
+ return x
+ end
+ return first_non_nil(...)
+ end
+
+ -- if input_path is youtube, generate our own URL
+ youtube_id1 = string.match(input_path, "https?://youtu%.be/([%a%d%-_]+).*")
+ youtube_id2 = string.match(input_path, "https?://w?w?w?%.?youtube%.com/v/([%a%d%-_]+).*")
+ youtube_id3 = string.match(input_path, "https?://w?w?w?%.?youtube%.com/watch%?v=([%a%d%-_]+).*")
+ youtube_id4 = string.match(input_path, "https?://w?w?w?%.?youtube%.com/embed/([%a%d%-_]+).*")
+ youtube_id = youtube_id1 or youtube_id2 or youtube_id3 or youtube_id4
+
+ if youtube_id then
+ -- the hqdefault.jpg thumbnail should always exist, since it's used on the search result page
+ return "https://i.ytimg.com/vi/" .. youtube_id .. "/hqdefault.jpg"
+ end
+
+ --otherwise proceed with the slower `youtube-dl -J` method
+ if not ytdl.searched then --search for youtude-dl in mpv's config directory
+ local exesuf = (package.config:sub(1, 1) == "\\") and ".exe" or ""
+ local ytdl_mcd = mp.find_config_file("youtube-dl")
+ if not (ytdl_mcd == nil) then
+ msg.error("found youtube-dl at: " .. ytdl_mcd)
+ ytdl.path = ytdl_mcd
+ end
+ ytdl.searched = true
+ end
+ local command = { ytdl.path, "--no-warnings", "--no-playlist", "-J", input_path }
+ local es, json, result = exec(command)
+
+ if (es < 0) or (json == nil) or (json == "") then
+ msg.error("fetching thumbnail url with youtube-dl failed for" .. input_path)
+ return input_path
+ end
+ local json, err = utils.parse_json(json)
+ if json == nil then
+ msg.error("failed to parse json for youtube-dl thumbnail: " .. err)
+ return input_path
+ end
+
+ if (json.thumbnail == nil) or (json.thumbnail == "") then
+ msg.error("no thumbnail url from youtube-dl.")
+ return input_path
+ end
+ return json.thumbnail
+end
+
+function thumbnail_command(input_path, width, height, take_thumbnail_at, output_path, accurate, with_mpv)
+ local vf = string.format(
+ "%s,%s",
+ string.format("scale=iw*min(1\\,min(%d/iw\\,%d/ih)):-2", width, height),
+ string.format("pad=%d:%d:(%d-iw)/2:(%d-ih)/2:color=0x00000000", width, height, width, height)
+ )
+ local out = {}
+ local add = function(table)
+ out = append_table(out, table)
+ end
+
+ if input_path:find("^https?://") and not is_blacklisted(input_path) then
+ -- returns the original input_path on failure
+ input_path = ytdl_thumbnail_url(input_path)
+ end
+
+ if input_path:find("^archive://") or input_path:find("^edl://") then
+ with_mpv = true
+ end
+
+ if not with_mpv then
+ out = { "ffmpeg" }
+ if is_video(input_path) then
+ if string.sub(take_thumbnail_at, -1) == "%" then
+ --if only fucking ffmpeg supported percent-style seeking
+ local res = utils.subprocess({
+ args = {
+ "ffprobe",
+ "-v",
+ "error",
+ "-show_entries",
+ "format=duration",
+ "-of",
+ "default=noprint_wrappers=1:nokey=1",
+ input_path,
+ },
+ cancellable = false,
+ })
+ if res.status == 0 then
+ local duration = tonumber(string.match(res.stdout, "^%s*(.-)%s*$"))
+ if duration then
+ local percent = tonumber(string.sub(take_thumbnail_at, 1, -2))
+ local start = tostring(duration * percent / 100)
+ add({ "-ss", start })
+ end
+ end
+ else
+ add({ "-ss", take_thumbnail_at })
+ end
+ end
+ if not accurate then
+ add({ "-noaccurate_seek" })
+ end
+ add({
+ "-i",
+ input_path,
+ "-vf",
+ vf,
+ "-map",
+ "v:0",
+ "-f",
+ "rawvideo",
+ "-pix_fmt",
+ "bgra",
+ "-c:v",
+ "rawvideo",
+ "-frames:v",
+ "1",
+ "-y",
+ "-loglevel",
+ "quiet",
+ output_path,
+ })
+ else
+ out = { "mpv", input_path }
+ if take_thumbnail_at ~= "0" and is_video(input_path) then
+ if not accurate then
+ add({ "--hr-seek=no" })
+ end
+ add({ "--start=" .. take_thumbnail_at })
+ end
+ add({
+ "--no-config",
+ "--msg-level=all=no",
+ "--vf=lavfi=[" .. vf .. ",format=bgra]",
+ "--audio=no",
+ "--sub=no",
+ "--frames=1",
+ "--image-display-duration=0",
+ "--of=rawvideo",
+ "--ovc=rawvideo",
+ "--o=" .. output_path,
+ })
+ end
+ return out
+end
+
+function generate_thumbnail(thumbnail_job)
+ if file_exists(thumbnail_job.output_path) then
+ return true
+ end
+
+ local dir, _ = utils.split_path(thumbnail_job.output_path)
+ local tmp_output_path = utils.join_path(dir, script_id)
+
+ local command = thumbnail_command(
+ thumbnail_job.input_path,
+ thumbnail_job.width,
+ thumbnail_job.height,
+ thumbnail_job.take_thumbnail_at,
+ tmp_output_path,
+ thumbnail_job.accurate,
+ thumbnail_job.with_mpv
+ )
+
+ local res = utils.subprocess({ args = command, cancellable = false })
+ --"atomically" generate the output to avoid loading half-generated thumbnails (results in crashes)
+ if res.status == 0 then
+ local info = utils.file_info(tmp_output_path)
+ if not info or not info.is_file or info.size == 0 then
+ return false
+ end
+ if os.rename(tmp_output_path, thumbnail_job.output_path) then
+ return true
+ end
+ end
+ return false
+end
+
+function handle_events(wait)
+ e = mp.wait_event(wait)
+ while e.event ~= "none" do
+ if e.event == "shutdown" then
+ return false
+ elseif e.event == "client-message" then
+ if e.args[1] == "push-thumbnail-front" or e.args[1] == "push-thumbnail-back" then
+ local thumbnail_job = {
+ requester = e.args[2],
+ input_path = e.args[3],
+ width = tonumber(e.args[4]),
+ height = tonumber(e.args[5]),
+ take_thumbnail_at = e.args[6],
+ output_path = e.args[7],
+ accurate = (e.args[8] == "true"),
+ with_mpv = (e.args[9] == "true"),
+ }
+ if e.args[1] == "push-thumbnail-front" then
+ jobs_queue[#jobs_queue + 1] = thumbnail_job
+ else
+ table.insert(jobs_queue, 1, thumbnail_job)
+ end
+ end
+ end
+ e = mp.wait_event(0)
+ end
+ return true
+end
+
+local registration_timeout = 2 -- seconds
+local registration_period = 0.2
+
+-- shitty custom event loop because I can't figure out a better way
+-- works pretty well though
+function mp_event_loop()
+ local start_time = mp.get_time()
+ local sleep_time = registration_period
+ local last_broadcast_time = -registration_period
+ local broadcast_func
+ broadcast_func = function()
+ local now = mp.get_time()
+ if now >= start_time + registration_timeout then
+ mp.commandv("script-message", "thumbnails-generator-broadcast", mp.get_script_name())
+ sleep_time = 1e20
+ broadcast_func = function() end
+ elseif now >= last_broadcast_time + registration_period then
+ mp.commandv("script-message", "thumbnails-generator-broadcast", mp.get_script_name())
+ last_broadcast_time = now
+ end
+ end
+
+ while true do
+ if not handle_events(sleep_time) then
+ return
+ end
+ broadcast_func()
+ while #jobs_queue > 0 do
+ local thumbnail_job = jobs_queue[#jobs_queue]
+ if not failed[thumbnail_job.output_path] then
+ if generate_thumbnail(thumbnail_job) then
+ mp.commandv(
+ "script-message-to",
+ thumbnail_job.requester,
+ "thumbnail-generated",
+ thumbnail_job.output_path
+ )
+ else
+ failed[thumbnail_job.output_path] = true
+ end
+ end
+ jobs_queue[#jobs_queue] = nil
+ if not handle_events(0) then
+ return
+ end
+ broadcast_func()
+ end
+ end
+end
diff --git a/ar/.config/mpv/scripts/history.lua b/ar/.config/mpv/scripts/history.lua
new file mode 100644
index 0000000..974f42b
--- /dev/null
+++ b/ar/.config/mpv/scripts/history.lua
@@ -0,0 +1,138 @@
+-- public domain
+-- http://git.smrk.net/mpv-scripts/file/history.lua.html
+-- Corrections and productive feedback appreciated, publicly
+-- (<public@smrk.net>, inbox.smrk.net) or in private.
+
+local mpu = require("mp.utils")
+local sqlite3 = require("lsqlite3")
+
+local history_db_path = mpu.join_path(os.getenv("XDG_DATA_HOME"), "history/mpv.sqlite")
+
+local db = nil
+local last_insert_id = nil
+
+local function opendb()
+ local d, errcode, errmsg = sqlite3.open(history_db_path)
+ if not d then
+ error(("Failed to open %s: %d (%s)"):format(history_db_path, errcode, errmsg))
+ end
+
+ db = d
+
+ local sql = [=[
+ CREATE TABLE IF NOT EXISTS loaded_items(
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ path TEXT NOT NULL,
+ filename TEXT NOT NULL,
+ title TEXT NOT NULL,
+ time_pos REAL,
+ date DATE NOT NULL
+ );
+ ]=]
+
+ if db:exec(sql) ~= sqlite3.OK then
+ error(("sqlite: %d (%s)"):format(db:errcode(), db:errmsg()))
+ end
+end
+
+local function dbexec(sql, func, udata)
+ if not db then
+ opendb()
+ end
+ if db:exec(sql, func, udata) ~= sqlite3.OK then
+ error(("sqlite: %d (%s)"):format(db:errcode(), db:errmsg()))
+ end
+end
+
+local function abspath()
+ local path = mp.get_property("path")
+ if path:find("://") then
+ return path
+ else
+ return mpu.join_path(mp.get_property("working-directory"), path)
+ end
+end
+
+mp.register_event("file-loaded", function()
+ dbexec(
+ ([=[
+ INSERT INTO loaded_items (path, filename, title, date)
+ VALUES(
+ "%s",
+ "%s",
+ "%s",
+ unixepoch()
+ );
+ SELECT LAST_INSERT_ROWID();
+ ]=]):format(abspath(), mp.get_property("filename"), mp.get_property("media-title")),
+ function(_udata, _cols, values, _names)
+ last_insert_id = values[1]
+ return 0
+ end,
+ nil
+ )
+end)
+
+mp.add_hook("on_unload", 50, function(_hook)
+ local timepos = mp.get_property("audio-pts") or mp.get_property("time-pos")
+ if timepos then
+ dbexec(([=[
+ UPDATE loaded_items
+ SET time_pos = %g
+ WHERE id = %d;
+ ]=]):format(timepos, last_insert_id))
+ end
+end)
+
+mp.register_event("shutdown", function()
+ if db then
+ db:close()
+ end
+end)
+
+mp.add_key_binding("ctrl+r", "history-resume", function()
+ dbexec(
+ ([=[
+ SELECT time_pos FROM loaded_items
+ WHERE time_pos NOTNULL AND title = "%s"
+ ORDER BY date DESC
+ LIMIT 1;
+ ]=]):format(mp.get_property("media-title")),
+ function(_udata, _cols, values, _names)
+ if values[1] then
+ mp.commandv("seek", values[1], "absolute", "exact")
+ end
+ return 0
+ end,
+ nil
+ )
+end)
+
+mp.add_key_binding("alt+r", "play-recent", function()
+ local items = {}
+ dbexec(
+ [=[
+ SELECT DISTINCT path, title FROM loaded_items
+ ORDER BY date DESC;
+ ]=],
+ function(_udata, _cols, values, _names)
+ if values[1] then
+ items[#items + 1] = values[1] .. "\x1f" .. values[2]
+ end
+ return 0
+ end,
+ nil
+ )
+ if items[1] then
+ local dmenu = mp.command_native({
+ name = "subprocess",
+ args = { "dmenu", "-l", "20" },
+ capture_stdout = true,
+ playback_only = false,
+ stdin_data = table.concat(items, "\n"),
+ })
+ if dmenu.status == 0 then
+ mp.commandv("loadfile", dmenu.stdout:sub(1, dmenu.stdout:find("\x1f") - 1))
+ end
+ end
+end)
diff --git a/ar/.config/mpv/scripts/mdmenu.lua b/ar/.config/mpv/scripts/mdmenu.lua
new file mode 100644
index 0000000..1a0513c
--- /dev/null
+++ b/ar/.config/mpv/scripts/mdmenu.lua
@@ -0,0 +1,285 @@
+--[[
+ This file is part of mdmenu.
+
+ mdmenu is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ mdmenu is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
+ for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with mdmenu. If not, see <https://www.gnu.org/licenses/>.
+]]
+
+local msg = require("mp.msg")
+local mpopt = require("mp.options")
+local utils = require("mp.utils")
+
+local state = {
+ playlist = nil,
+ playlist_current = nil,
+ tracklist = nil,
+ chapters = nil,
+ chapters_raw = nil,
+ wid = nil,
+}
+
+local opt = {
+ embed = true,
+ preselect = false,
+ cmd = { "dmenu", "-i", "-l", "16" },
+
+ debug = false,
+}
+
+local zassert = function() end
+local ob = function(b)
+ return b and "[" or " "
+end
+local cb = function(b)
+ return b and "]" or " "
+end
+
+local function format_time(t)
+ local h = math.floor(t / (60 * 60))
+ t = t - (h * 60 * 60)
+ local m = math.floor(t / 60)
+ local s = t - (m * 60)
+ return string.format("%.2d:%.2d:%.2d", h, m, s)
+end
+
+local function humantime_to_sec(str)
+ zassert(string.len(str) >= 8)
+ local h = tonumber(string.sub(str, 1, 2))
+ local m = tonumber(string.sub(str, 4, 5))
+ local s = tonumber(string.sub(str, 7, 8))
+ if h and m and s and string.sub(str, 3, 3) == ":" and string.sub(str, 6, 6) == ":" then
+ return (h * 60 * 60) + (m * 60) + s
+ end
+ return nil
+end
+
+local function grab_xid(kind, isconfigured)
+ zassert(kind == "vo-configured")
+ state.wid = nil -- clear it to account for runtime vo change
+ if isconfigured then
+ local wid = mp.get_property("window-id")
+ local vo_null = (wid == nil) and (mp.get_property("current-vo") == "null")
+ if wid == nil and not vo_null then
+ local pid = mp.get_property("pid")
+ local r = mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ args = { "xdo", "id", "-p", pid },
+ })
+ if r.status == 0 and string.len(r.stdout) > 0 then
+ wid = string.match(r.stdout, "0x%x+")
+ end
+ end
+ if wid then
+ state.wid = wid
+ elseif not vo_null then
+ msg.warn("couldn't get mpv's xwindow id. make sure `xdo` is installed.")
+ end
+ end
+ msg.debug("[grab_xid]: isconfigured = " .. tostring(isconfigured) .. " wid = " .. tostring(state.wid))
+end
+
+local function set_playlist(kind, plist)
+ zassert(kind == "playlist")
+ local s = ""
+ local f = "%" .. (string.len(#plist) + ob(true):len() + cb(true):len()) .. "s"
+ state.playlist_current = nil
+ for k, pl in ipairs(plist) do
+ state.playlist_current = pl.current and k or state.playlist_current
+ s = s .. string.format(f, ob(pl.current) .. k .. cb(pl.current)) .. " "
+ s = s .. (pl.title or select(2, utils.split_path(pl.filename))) .. "\n"
+ end
+ state.playlist = s
+end
+
+local function set_tracklist(kind, tlist)
+ zassert(kind == "track-list")
+ local s = ""
+ for _, t in ipairs(tlist) do
+ s = s .. ob(t.selected) .. string.sub(t.type, 1, 1)
+ s = s .. t.id .. cb(t.selected) .. " "
+
+ if t.title then
+ s = s .. t.title .. " "
+ end
+ if t.lang then
+ s = s .. t.lang .. " "
+ end
+ s = s .. "\n"
+ end
+ state.tracklist = s
+end
+
+local function set_chapter_list(kind, c)
+ zassert(kind == "chapter-list")
+ if c and #c > 0 then
+ local s = ""
+ for _, ch in ipairs(c) do
+ s = s .. format_time(ch.time) .. " "
+ s = s .. ch.title .. "\n"
+ end
+ state.chapters = s
+ state.chapters_raw = c
+ else
+ state.chapters = nil
+ state.chapters_raw = nil
+ end
+end
+
+local function table_append(a, b)
+ for _, v in ipairs(b) do
+ table.insert(a, v)
+ end
+end
+
+local function call_dmenu(stdin, extra_arg)
+ local cmd = {}
+ table_append(cmd, opt.cmd)
+ if state.wid then
+ table.insert(cmd, "-w")
+ table.insert(cmd, state.wid)
+ end
+ if extra_arg then
+ table_append(cmd, extra_arg)
+ end
+ msg.debug("[call_dmenu]: " .. table.concat(cmd, " "))
+ return mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ stdin_data = stdin,
+ capture_stdout = true,
+ args = cmd,
+ })
+end
+
+local function menu_playlist()
+ if state.playlist == nil then
+ return
+ end
+ local narg = nil
+ if opt.preselect and state.playlist_current ~= nil then
+ narg = { "-n", tostring(state.playlist_current - 1) }
+ end
+ local r = call_dmenu(state.playlist, narg)
+ if r.status == 0 and string.len(r.stdout) > 2 then
+ s = string.match(r.stdout, "[%s%[]*(%d+)")
+ if tonumber(s) then
+ mp.set_property("playlist-pos-1", s)
+ else
+ msg.warn("bad playlist position: " .. r.stdout)
+ end
+ end
+end
+
+local function menu_tracklist()
+ if state.tracklist == nil then
+ return
+ end
+
+ local r = call_dmenu(state.tracklist)
+ if r.status == 0 and string.len(r.stdout) > 4 then
+ local active = string.sub(r.stdout, 1, 1) == "["
+ local type = string.sub(r.stdout, 2, 2)
+ local cmd = { ["v"] = "vid", ["a"] = "audio", ["s"] = "sub" }
+ local num = tonumber(string.sub(r.stdout, 3):match("%d+"))
+ local arg = { [false] = num, [true] = "no" }
+
+ if cmd[type] and num ~= nil then
+ mp.commandv("set", cmd[type], arg[active])
+ else
+ msg.warn("messed up input: " .. r.stdout)
+ end
+ end
+end
+
+local function menu_chapters()
+ if state.chapters == nil then
+ return
+ end
+ local narg = nil
+ if opt.preselect then
+ local t = mp.get_property_native("time-pos") or 0
+ local n = 0
+ for i, c in ipairs(state.chapters_raw) do
+ if t > c.time then
+ n = i - 1
+ end
+ end
+ narg = { "-n", tostring(n) }
+ end
+
+ local r = call_dmenu(state.chapters, narg)
+ if r.status == 0 and string.len(r.stdout) > 8 then
+ local t = humantime_to_sec(r.stdout)
+ if t then
+ mp.set_property("time-pos", t)
+ else
+ msg.warn("bad chapter position: " .. r.stdout)
+ end
+ end
+end
+
+local function menu_bindings()
+ local s = ""
+ local bind = mp.get_property_native("input-bindings")
+ for k, v in pairs(bind) do
+ s = s .. string.format("%-16s ", v.key) .. v.cmd .. "\n"
+ end
+ local r = call_dmenu(s)
+ -- if (r.status == 0 and string.len(r.stdout) > 0) then
+ -- local _, cmd = string.match(r.stdout, "(%w+)%s+(.+)\n");
+ -- if (cmd ~= nil and string.len(cmd) > 0) then
+ -- mp.command(cmd)
+ -- end
+ -- end
+end
+
+local function init()
+ mpopt.read_options(opt, "mdmenu")
+ if type(opt.cmd) == "string" then -- what a pain
+ local s = opt.cmd
+ opt.cmd = {}
+ for arg in string.gmatch(s, "[^,]+") do
+ table.insert(opt.cmd, arg)
+ end
+ end
+
+ if opt.debug then
+ msg.debug("[ASSERTIONS] enabled")
+ zassert = assert
+ else
+ zassert(false)
+ end
+
+ -- grab mpv's xwindow id
+ if opt.embed then
+ -- HACK: mpv doesn't open the window instantly by default.
+ -- so wait for 'vo-configured' to be true before trying to
+ -- grab the xid.
+ mp.observe_property("vo-configured", "native", grab_xid)
+ end
+
+ mp.observe_property("playlist", "native", set_playlist)
+ mp.add_key_binding(nil, "playlist", menu_playlist)
+
+ mp.observe_property("track-list", "native", set_tracklist)
+ mp.add_key_binding(nil, "tracklist", menu_tracklist)
+
+ mp.observe_property("chapter-list", "native", set_chapter_list)
+ mp.add_key_binding(nil, "chapters", menu_chapters)
+
+ mp.add_key_binding(nil, "bindings", menu_bindings)
+end
+
+init()
diff --git a/ar/.config/mpv/scripts/misc.lua b/ar/.config/mpv/scripts/misc.lua
new file mode 100644
index 0000000..823b6fa
--- /dev/null
+++ b/ar/.config/mpv/scripts/misc.lua
@@ -0,0 +1,594 @@
+--[[
+
+ https://github.com/stax76/mpv-scripts
+
+ This script consist of various small unrelated features.
+
+ Not used code sections can be removed.
+
+ Bindings must be added manually to input.conf.
+
+
+
+ Show media info on screen
+ -------------------------
+ Prints detailed media info on the screen.
+
+ Depends on the CLI tool 'mediainfo':
+ https://mediaarea.net/en/MediaInfo/Download
+
+ In input.conf add:
+ i script-message-to misc print-media-info
+
+
+
+ Load files/URLs from clipboard
+ ------------------------------
+ Loads one or multiple files/URLs from the clipboard.
+ The clipboard format can be of type string or file object.
+ Allows appending to the playlist.
+ On Linux requires xclip being installed.
+
+ In input.conf add:
+ ctrl+v script-message-to misc load-from-clipboard
+ ctrl+V script-message-to misc append-from-clipboard
+
+
+
+ Cycle audio and subtitle tracks
+ -------------------------------
+ If there are 20+ subtitle tracks, it's annoying cycling through all
+ of them. This feature allows you to cycle only through languages
+ you actually know.
+
+ In mpv.conf define your known languages:
+ alang = de,deu,ger,en,eng #German/English
+ slang = en,eng,de,deu,ger #English/German
+
+ If you don't know the language IDs, use the terminal,
+ mpv prints the language IDs there whenever a video file is loaded.
+
+ In input.conf add:
+ SHARP script-message-to misc cycle-known-tracks audio
+ j script-message-to misc cycle-known-tracks sub up
+ J script-message-to misc cycle-known-tracks sub down
+
+ ~~/script-opts/misc.conf:
+ #include_no_audio=no
+ #include_no_sub=yes
+
+ ## If more than 5 tracks exist, only known are cycled,
+ ## define 0 to always cycle only known tracks.
+ #max_audio_track_count=5
+ #max_sub_track_count=5
+
+ If you prefer a menu:
+ https://github.com/stax76/mpv-scripts?tab=readme-ov-file#command_palette
+ https://github.com/stax76/mpv-scripts#search-menu
+ https://github.com/dyphire/mpv-scripts/blob/main/track-list.lua
+ https://codeberg.org/NRK/mpv-toolbox/src/branch/master/mdmenu
+ https://github.com/tomasklaen/uosc
+
+ The code was originally written by stax76, it was later
+ greatly improved by kaoneko making it much shorter.
+
+
+ Jump to a random position in the playlist
+ -----------------------------------------
+ In input.conf add:
+ ctrl+r script-message-to misc playlist-random
+
+ If pos=last it jumps to first instead of random.
+
+
+
+ Quick Bookmark
+ --------------
+ Creates or restores a bookmark. Supports one bookmark per video.
+
+ Usage:
+ Create a folder in the following location:
+ ~~/script-settings/quick-bookmark/
+ Or create it somewhere else, config at:
+ ~~/script-opts/misc.conf:
+ quick_bookmark_folder=<folder path>
+
+ In input.conf add:
+ ctrl+q script-message-to misc quick-bookmark
+
+
+
+ Playlist Next/Prev
+ ------------------
+ Like the regular playlist-next/playlist-prev, but does not restart playback
+ of the first or last file, in case the first or last track already plays,
+ instead shows a OSD message.
+
+ F11 script-message-to misc playlist-prev # Go to previous file in playlist
+ F12 script-message-to misc playlist-next # Go to next file in playlist
+
+
+
+ Playlist First/Last
+ -------------------
+ Navigates to the first or last track in the playlist,
+ in case the first or last track already plays, it does not
+ restart playback, instead shows a OSD message.
+
+ Home script-message-to misc playlist-first # Go to first file in playlist
+ End script-message-to misc playlist-last # Go to last file in playlist
+
+
+
+ Restart mpv
+ -----------
+ Restarts mpv restoring the properties path, time-pos,
+ pause and volume, the playlist is not restored.
+
+ r script-message-to misc restart-mpv
+
+
+
+ Execute Lua code
+ ----------------
+ Allows to execute Lua Code directly from input.conf.
+
+ It's necessary to add a binding to input.conf:
+ #Navigates to the last file in the playlist
+ END script-message-to misc execute-lua-code "mp.set_property_number('playlist-pos', mp.get_property_number('playlist-count') - 1)"
+
+
+
+ When seeking displays position and duration like so:
+ ----------------------------------------------------
+ 70:00 / 80:00
+
+ Which is different from most players which use:
+
+ 01:10:00 / 01:20:00
+
+ input.conf:
+ Right no-osd seek 5; script-message-to misc show-position
+
+]]
+--
+
+----- options
+
+local o = {
+ -- Cycle audio and subtitle tracks
+ include_no_audio = false,
+ include_no_sub = true,
+ max_audio_track_count = 5,
+ max_sub_track_count = 5,
+ -- Quick Bookmark
+ quick_bookmark_folder = "~~/script-settings/quick-bookmark/",
+}
+
+local opt = require("mp.options")
+opt.read_options(o)
+
+----- string
+
+function is_empty(input)
+ if input == nil or input == "" then
+ return true
+ end
+end
+
+function contains(input, find)
+ if not is_empty(input) and not is_empty(find) then
+ return input:find(find, 1, true)
+ end
+end
+
+function trim(input)
+ if not is_empty(input) then
+ return input:match("^%s*(.-)%s*$")
+ end
+end
+
+function split(input, sep)
+ local tbl = {}
+
+ if not is_empty(input) then
+ for str in string.gmatch(input, "([^" .. sep .. "]+)") do
+ table.insert(tbl, str)
+ end
+ end
+
+ return tbl
+end
+
+----- list
+
+function list_contains(list, value)
+ for _, v in pairs(list) do
+ if v == value then
+ return true
+ end
+ end
+
+ return false
+end
+
+----- math
+
+function round(value)
+ return value >= 0 and math.floor(value + 0.5) or math.ceil(value - 0.5)
+end
+
+----- file
+
+function file_exists(path)
+ if is_empty(path) then
+ return false
+ end
+ local file = io.open(path, "r")
+
+ if file ~= nil then
+ io.close(file)
+ return true
+ end
+end
+
+function file_read(file_path)
+ local file = assert(io.open(file_path, "r"))
+ local content = file:read("*all")
+ file:close()
+ return content
+end
+
+function file_write(path, content)
+ local file = assert(io.open(path, "w"))
+ file:write(content)
+ file:close()
+end
+
+----- shared
+
+local is_windows = package.config:sub(1, 1) == "\\"
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+
+function get_temp_dir()
+ if is_windows then
+ return os.getenv("TEMP") .. "\\"
+ else
+ return "/tmp/"
+ end
+end
+
+----- Jump to a random position in the playlist
+
+mp.register_script_message("playlist-random", function()
+ local count = mp.get_property_number("playlist-count")
+ math.randomseed(os.time())
+ local new_pos = math.random(0, count - 1)
+ local current_pos = mp.get_property_number("playlist-pos")
+
+ if current_pos == count - 1 then
+ new_pos = 0
+ end
+
+ mp.set_property_number("playlist-pos", new_pos)
+end)
+
+----- Execute Lua code
+
+mp.register_script_message("execute-lua-code", function(code)
+ loadstring(code)()
+end)
+
+----- Alternative seek OSD message
+
+function pad_zero(value)
+ local value = round(value)
+
+ if value > 9 then
+ return "" .. value
+ else
+ return "0" .. value
+ end
+end
+
+function format_pos(value)
+ local seconds = round(value)
+
+ if seconds < 0 then
+ seconds = 0
+ end
+
+ local pos_min_floor = math.floor(seconds / 60)
+ local sec_rest = seconds - pos_min_floor * 60
+
+ return pad_zero(pos_min_floor) .. ":" .. pad_zero(sec_rest)
+end
+
+function show_pos()
+ local position = mp.get_property_number("time-pos")
+ local duration = mp.get_property_number("duration")
+
+ if position > duration then
+ position = duration
+ end
+
+ if position ~= 0 then
+ local percent = math.floor(position / duration * 100 + 0.5)
+ mp.osd_message(format_pos(position) .. " / " .. format_pos(duration) .. " (" .. percent .. "%)")
+ end
+end
+
+mp.register_script_message("show-position", function(mode)
+ mp.add_timeout(0.05, show_pos)
+end)
+
+----- Print media info on screen
+
+local media_info_cache = {}
+
+function show_text(text, duration, font_size)
+ mp.command('show-text "${osd-ass-cc/0}{\\\\fs' .. font_size .. "}${osd-ass-cc/1}" .. text .. '" ' .. duration)
+end
+
+function get_media_info()
+ local path = mp.get_property("path")
+
+ if media_info_cache[path] then
+ return media_info_cache[path]
+ end
+
+ local media_info_format =
+ [[General;N: %FileNameExtension%\\nG: %Format%, %FileSize/String%, %Duration/String%, %OverallBitRate/String%, %Recorded_Date%\\n
+Video;V: %Format%, %Format_Profile%, %Width%x%Height%, %BitRate/String%, %FrameRate% FPS\\n
+Audio;A: %Language/String%, %Format%, %Format_Profile%, %BitRate/String%, %Channel(s)% ch, %SamplingRate/String%, %Title%\\n
+Text;S: %Language/String%, %Format%, %Format_Profile%, %Title%\\n]]
+
+ local format_file = get_temp_dir() .. "media-info-format-2.txt"
+
+ if not file_exists(format_file) then
+ file_write(format_file, media_info_format)
+ end
+
+ if contains(path, "://") or not file_exists(path) then
+ return
+ end
+
+ local proc_result = mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ args = { "mediainfo", "--inform=file://" .. format_file, path },
+ })
+
+ if proc_result.status == 0 then
+ local output = proc_result.stdout
+
+ output = string.gsub(output, ", , ,", ",")
+ output = string.gsub(output, ", ,", ",")
+ output = string.gsub(output, ": , ", ": ")
+ output = string.gsub(output, ", \\n\r*\n", "\\n")
+ output = string.gsub(output, "\\n\r*\n", "\\n")
+ output = string.gsub(output, ", \\n", "\\n")
+ output = string.gsub(output, "%.000 FPS", " FPS")
+ output = string.gsub(output, "MPEG Audio, Layer 3", "MP3")
+
+ media_info_cache[path] = output
+
+ return output
+ end
+end
+
+mp.register_script_message("print-media-info", function()
+ show_text(get_media_info(), 5000, 16)
+end)
+
+----- Playlist Next/Prev
+
+mp.register_script_message("playlist-next", function()
+ local count = mp.get_property_number("playlist-count")
+ if count == 0 then
+ return
+ end
+ local pos = mp.get_property_number("playlist-pos")
+
+ if pos == count - 1 then
+ mp.osd_message("Already last track")
+ return
+ end
+
+ mp.set_property_number("playlist-pos", pos + 1)
+end)
+
+mp.register_script_message("playlist-prev", function()
+ local count = mp.get_property_number("playlist-count")
+ if count == 0 then
+ return
+ end
+ local pos = mp.get_property_number("playlist-pos")
+
+ if pos == 0 then
+ mp.osd_message("Already first track")
+ return
+ end
+
+ mp.set_property_number("playlist-pos", pos - 1)
+end)
+
+----- Playlist First/Last
+
+mp.register_script_message("playlist-first", function()
+ local count = mp.get_property_number("playlist-count")
+ if count == 0 then
+ return
+ end
+ local pos = mp.get_property_number("playlist-pos")
+
+ if pos == 0 then
+ mp.osd_message("Already first track")
+ return
+ end
+
+ mp.set_property_number("playlist-pos", 0)
+end)
+
+mp.register_script_message("playlist-last", function()
+ local count = mp.get_property_number("playlist-count")
+ if count == 0 then
+ return
+ end
+ local pos = mp.get_property_number("playlist-pos")
+
+ if pos == count - 1 then
+ mp.osd_message("Already last track")
+ return
+ end
+
+ mp.set_property_number("playlist-pos", count - 1)
+end)
+
+----- Load files from clipboard
+
+function loadfiles(mode)
+ if is_windows then
+ local ps_code = [[
+ Add-Type -AssemblyName System.Windows.Forms
+ $containsFiles = [Windows.Forms.Clipboard]::ContainsFileDropList()
+
+ if ($containsFiles) {
+ [Windows.Forms.Clipboard]::GetFileDropList() -join [Environment]::NewLine
+ } else {
+ Get-Clipboard
+ }
+ ]]
+
+ proc_args = { "powershell", "-command", ps_code }
+ else
+ proc_args = { "xclip", "-o", "-selection", "clipboard" }
+ end
+
+ subprocess = {
+ name = "subprocess",
+ args = proc_args,
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ }
+
+ proc_result = mp.command_native(subprocess)
+
+ if proc_result.status < 0 then
+ msg.error("Error string: " .. proc_result.error_string)
+ msg.error("Error stderr: " .. proc_result.stderr)
+ return
+ end
+
+ proc_output = trim(proc_result.stdout)
+
+ if is_empty(proc_output) then
+ return
+ end
+
+ if contains(proc_output, "\n") then
+ mp.commandv("loadlist", "memory://" .. proc_output, mode)
+ else
+ mp.commandv("loadfile", proc_output, mode)
+ end
+end
+
+mp.register_script_message("load-from-clipboard", function()
+ loadfiles("replace")
+end)
+
+mp.register_script_message("append-from-clipboard", function()
+ loadfiles("append")
+end)
+
+----- Restart mpv
+
+mp.register_script_message("restart-mpv", function()
+ local restart_args = {
+ "mpv",
+ "--pause=" .. mp.get_property("pause"),
+ "--volume=" .. mp.get_property("volume"),
+ }
+
+ local playlist_pos = mp.get_property_number("playlist-pos")
+
+ if playlist_pos > -1 then
+ table.insert(restart_args, "--start=" .. mp.get_property("time-pos"))
+ table.insert(restart_args, mp.get_property("path"))
+ end
+
+ mp.command_native({
+ name = "subprocess",
+ playback_only = false,
+ detach = true,
+ args = restart_args,
+ })
+
+ mp.command("quit")
+end)
+
+----- Cycle audio and subtitle tracks
+
+mp.register_script_message("cycle-known-tracks", function(mode, dir)
+ local m = mode:sub(1, 1)
+ local lang_list = {}
+ for _, lang in pairs(mp.get_property_native(m .. "lang")) do
+ lang_list[lang:gsub(" ", "")] = true
+ end
+ local track_list = mp.get_property_native("track-list")
+ local id_list = { o["include_no_" .. mode] and "no" or nil }
+ local count = 0
+ local max_count = o["max_" .. mode .. "_track_count"]
+
+ for _, track in pairs(track_list) do
+ if track.type == mode then
+ count = count + 1
+ if lang_list[track.lang] or not track.lang or track.selected or not next(lang_list) then
+ table.insert(id_list, track.id)
+ end
+ end
+ end
+
+ if #id_list < 2 then
+ return
+ elseif count <= max_count then
+ mp.command("cycle " .. mode .. " " .. (dir or ""))
+ else
+ mp.command("cycle-values " .. (dir == "down" and "!reverse " or "") .. m .. "id " .. table.concat(id_list, " "))
+ end
+end)
+
+----- Quick Bookmark
+
+mp.register_script_message("quick-bookmark", function()
+ local path = mp.get_property("path")
+
+ if is_empty(path) then
+ return
+ end
+
+ local folder = mp.command_native({ "expand-path", o.quick_bookmark_folder })
+
+ if utils.file_info(folder) == nil then
+ msg.error("Bookmark folder not found, create it at:\n" .. folder)
+ return
+ end
+
+ if file_exists(path) then
+ _, path = utils.split_path(path)
+ path = utils.join_path(folder, path)
+ else
+ path = utils.join_path(folder, string.gsub(path, "[/\\:]", ""))
+ end
+
+ if file_exists(path) then
+ mp.set_property_number("time-pos", tonumber(file_read(path)))
+ os.remove(path)
+ else
+ file_write(path, mp.get_property("time-pos"))
+ mp.osd_message("Bookmark saved")
+ end
+end)
diff --git a/ar/.config/mpv/scripts/modules.lua b/ar/.config/mpv/scripts/modules.lua
new file mode 100644
index 0000000..3fceb6e
--- /dev/null
+++ b/ar/.config/mpv/scripts/modules.lua
@@ -0,0 +1,5 @@
+local mpv_config_dir_path = require("mp").command_native({ "expand-path", "~~/" })
+function load(relative_path)
+ dofile(mpv_config_dir_path .. "/script-modules/" .. relative_path)
+end
+load("mpvSockets.lua")
diff --git a/ar/.config/mpv/scripts/mpv_crop_script.lua b/ar/.config/mpv/scripts/mpv_crop_script.lua
new file mode 100644
index 0000000..c410fd3
--- /dev/null
+++ b/ar/.config/mpv/scripts/mpv_crop_script.lua
@@ -0,0 +1,3173 @@
+--[[
+ Copyright (C) 2017 AMM
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+]]--
+--[[
+ mpv_crop_script.lua 0.5.0 - commit 472281e (branch master)
+ Built on 2018-09-30 14:22:46
+]]--
+--[[
+ Assorted helper functions, from checking falsey values to path utils
+ to escaping and wrapping strings.
+
+ Does not depend on other libs.
+]]--
+
+local assdraw = require 'mp.assdraw'
+local msg = require 'mp.msg'
+local utils = require 'mp.utils'
+
+-- Determine platform --
+ON_WINDOWS = (package.config:sub(1,1) ~= '/')
+
+-- Some helper functions needed to parse the options --
+function isempty(v) return (v == false) or (v == nil) or (v == "") or (v == 0) or (type(v) == "table" and next(v) == nil) end
+
+function divmod (a, b)
+ return math.floor(a / b), a % b
+end
+
+-- Better modulo
+function bmod( i, N )
+ return (i % N + N) % N
+end
+
+
+-- Path utils
+local path_utils = {
+ abspath = true,
+ split = true,
+ dirname = true,
+ basename = true,
+
+ isabs = true,
+ normcase = true,
+ splitdrive = true,
+ join = true,
+ normpath = true,
+ relpath = true,
+}
+
+-- Helpers
+path_utils._split_parts = function(path, sep)
+ local path_parts = {}
+ for c in path:gmatch('[^' .. sep .. ']+') do table.insert(path_parts, c) end
+ return path_parts
+end
+
+-- Common functions
+path_utils.abspath = function(path)
+ if not path_utils.isabs(path) then
+ local cwd = os.getenv("PWD") or utils.getcwd()
+ path = path_utils.join(cwd, path)
+ end
+ return path_utils.normpath(path)
+end
+
+path_utils.split = function(path)
+ local drive, path = path_utils.splitdrive(path)
+ -- Technically unix path could contain a \, but meh
+ local first_index, last_index = path:find('^.*[/\\]')
+
+ if last_index == nil then
+ return drive .. '', path
+ else
+ local head = path:sub(0, last_index-1)
+ local tail = path:sub(last_index+1)
+ if head == '' then head = sep end
+ return drive .. head, tail
+ end
+end
+
+path_utils.dirname = function(path)
+ local head, tail = path_utils.split(path)
+ return head
+end
+
+path_utils.basename = function(path)
+ local head, tail = path_utils.split(path)
+ return tail
+end
+
+path_utils.expanduser = function(path)
+ -- Expands the following from the start of the path:
+ -- ~ to HOME
+ -- ~~ to mpv config directory (first result of mp.find_config_file('.'))
+ -- ~~desktop to Windows desktop, otherwise HOME
+ -- ~~temp to Windows temp or /tmp/
+
+ local first_index, last_index = path:find('^.-[/\\]')
+ local head = path
+ local tail = ''
+
+ local sep = ''
+
+ if last_index then
+ head = path:sub(0, last_index-1)
+ tail = path:sub(last_index+1)
+ sep = path:sub(last_index, last_index)
+ end
+
+ if head == "~~desktop" then
+ head = ON_WINDOWS and path_utils.join(os.getenv('USERPROFILE'), 'Desktop') or os.getenv('HOME')
+ elseif head == "~~temp" then
+ head = ON_WINDOWS and os.getenv('TEMP') or (os.getenv('TMP') or '/tmp/')
+ elseif head == "~~" then
+ local mpv_config_dir = mp.find_config_file('.')
+ if mpv_config_dir then
+ head = path_utils.dirname(mpv_config_dir)
+ else
+ msg.warn('Could not find mpv config directory (using mp.find_config_file), using temp instead')
+ head = ON_WINDOWS and os.getenv('TEMP') or (os.getenv('TMP') or '/tmp/')
+ end
+ elseif head == "~" then
+ head = ON_WINDOWS and os.getenv('USERPROFILE') or os.getenv('HOME')
+ end
+
+ return path_utils.normpath(path_utils.join(head .. sep, tail))
+end
+
+
+if ON_WINDOWS then
+ local sep = '\\'
+ local altsep = '/'
+ local curdir = '.'
+ local pardir = '..'
+ local colon = ':'
+
+ local either_sep = function(c) return c == sep or c == altsep end
+
+ path_utils.isabs = function(path)
+ local prefix, path = path_utils.splitdrive(path)
+ return either_sep(path:sub(1,1))
+ end
+
+ path_utils.normcase = function(path)
+ return path:gsub(altsep, sep):lower()
+ end
+
+ path_utils.splitdrive = function(path)
+ if #path >= 2 then
+ local norm = path:gsub(altsep, sep)
+ if (norm:sub(1, 2) == (sep..sep)) and (norm:sub(3,3) ~= sep) then
+ -- UNC path
+ local index = norm:find(sep, 3)
+ if not index then
+ return '', path
+ end
+
+ local index2 = norm:find(sep, index + 1)
+ if index2 == index + 1 then
+ return '', path
+ elseif not index2 then
+ index2 = path:len()
+ end
+
+ return path:sub(1, index2-1), path:sub(index2)
+ elseif norm:sub(2,2) == colon then
+ return path:sub(1, 2), path:sub(3)
+ end
+ end
+ return '', path
+ end
+
+ path_utils.join = function(path, ...)
+ local paths = {...}
+
+ local result_drive, result_path = path_utils.splitdrive(path)
+
+ function inner(p)
+ local p_drive, p_path = path_utils.splitdrive(p)
+ if either_sep(p_path:sub(1,1)) then
+ -- Path is absolute
+ if p_drive ~= '' or result_drive == '' then
+ result_drive = p_drive
+ end
+ result_path = p_path
+ return
+ elseif p_drive ~= '' and p_drive ~= result_drive then
+ if p_drive:lower() ~= result_drive:lower() then
+ -- Different paths, ignore first
+ result_drive = p_drive
+ result_path = p_path
+ return
+ end
+ end
+
+ if result_path ~= '' and not either_sep(result_path:sub(-1)) then
+ result_path = result_path .. sep
+ end
+ result_path = result_path .. p_path
+ end
+
+ for i, p in ipairs(paths) do inner(p) end
+
+ -- add separator between UNC and non-absolute path
+ if result_path ~= '' and not either_sep(result_path:sub(1,1)) and
+ result_drive ~= '' and result_drive:sub(-1) ~= colon then
+ return result_drive .. sep .. result_path
+ end
+ return result_drive .. result_path
+ end
+
+ path_utils.normpath = function(path)
+ if path:find('\\\\.\\', nil, true) == 1 or path:find('\\\\?\\', nil, true) == 1 then
+ -- Device names and literal paths - return as-is
+ return path
+ end
+
+ path = path:gsub(altsep, sep)
+ local prefix, path = path_utils.splitdrive(path)
+
+ if path:find(sep) == 1 then
+ prefix = prefix .. sep
+ path = path:gsub('^[\\]+', '')
+ end
+
+ local comps = path_utils._split_parts(path, sep)
+
+ local i = 1
+ while i <= #comps do
+ if comps[i] == curdir then
+ table.remove(comps, i)
+ elseif comps[i] == pardir then
+ if i > 1 and comps[i-1] ~= pardir then
+ table.remove(comps, i)
+ table.remove(comps, i-1)
+ i = i - 1
+ elseif i == 1 and prefix:match('\\$') then
+ table.remove(comps, i)
+ else
+ i = i + 1
+ end
+ else
+ i = i + 1
+ end
+ end
+
+ if prefix == '' and #comps == 0 then
+ comps[1] = curdir
+ end
+
+ return prefix .. table.concat(comps, sep)
+ end
+
+ path_utils.relpath = function(path, start)
+ start = start or curdir
+
+ local start_abs = path_utils.abspath(path_utils.normpath(start))
+ local path_abs = path_utils.abspath(path_utils.normpath(path))
+
+ local start_drive, start_rest = path_utils.splitdrive(start_abs)
+ local path_drive, path_rest = path_utils.splitdrive(path_abs)
+
+ if path_utils.normcase(start_drive) ~= path_utils.normcase(path_drive) then
+ -- Different drives
+ return nil
+ end
+
+ local start_list = path_utils._split_parts(start_rest, sep)
+ local path_list = path_utils._split_parts(path_rest, sep)
+
+ local i = 1
+ for j = 1, math.min(#start_list, #path_list) do
+ if path_utils.normcase(start_list[j]) ~= path_utils.normcase(path_list[j]) then
+ break
+ end
+ i = j + 1
+ end
+
+ local rel_list = {}
+ for j = 1, (#start_list - i + 1) do rel_list[j] = pardir end
+ for j = i, #path_list do table.insert(rel_list, path_list[j]) end
+
+ if #rel_list == 0 then
+ return curdir
+ end
+
+ return path_utils.join(unpack(rel_list))
+ end
+
+else
+ -- LINUX
+ local sep = '/'
+ local curdir = '.'
+ local pardir = '..'
+
+ path_utils.isabs = function(path) return path:sub(1,1) == '/' end
+ path_utils.normcase = function(path) return path end
+ path_utils.splitdrive = function(path) return '', path end
+
+ path_utils.join = function(path, ...)
+ local paths = {...}
+
+ for i, p in ipairs(paths) do
+ if p:sub(1,1) == sep then
+ path = p
+ elseif path == '' or path:sub(-1) == sep then
+ path = path .. p
+ else
+ path = path .. sep .. p
+ end
+ end
+
+ return path
+ end
+
+ path_utils.normpath = function(path)
+ if path == '' then return curdir end
+
+ local initial_slashes = (path:sub(1,1) == sep) and 1
+ if initial_slashes and path:sub(2,2) == sep and path:sub(3,3) ~= sep then
+ initial_slashes = 2
+ end
+
+ local comps = path_utils._split_parts(path, sep)
+ local new_comps = {}
+
+ for i, comp in ipairs(comps) do
+ if comp == '' or comp == curdir then
+ -- pass
+ elseif (comp ~= pardir or (not initial_slashes and #new_comps == 0) or
+ (#new_comps > 0 and new_comps[#new_comps] == pardir)) then
+ table.insert(new_comps, comp)
+ elseif #new_comps > 0 then
+ table.remove(new_comps)
+ end
+ end
+
+ comps = new_comps
+ path = table.concat(comps, sep)
+ if initial_slashes then
+ path = sep:rep(initial_slashes) .. path
+ end
+
+ return (path ~= '') and path or curdir
+ end
+
+ path_utils.relpath = function(path, start)
+ start = start or curdir
+
+ local start_abs = path_utils.abspath(path_utils.normpath(start))
+ local path_abs = path_utils.abspath(path_utils.normpath(path))
+
+ local start_list = path_utils._split_parts(start_abs, sep)
+ local path_list = path_utils._split_parts(path_abs, sep)
+
+ local i = 1
+ for j = 1, math.min(#start_list, #path_list) do
+ if start_list[j] ~= path_list[j] then break
+ end
+ i = j + 1
+ end
+
+ local rel_list = {}
+ for j = 1, (#start_list - i + 1) do rel_list[j] = pardir end
+ for j = i, #path_list do table.insert(rel_list, path_list[j]) end
+
+ if #rel_list == 0 then
+ return curdir
+ end
+
+ return path_utils.join(unpack(rel_list))
+ end
+
+end
+-- Path utils end
+
+-- Check if path is local (by looking if it's prefixed by a proto://)
+local path_is_local = function(path)
+ local proto = path:match('(..-)://')
+ return proto == nil
+end
+
+
+function Set(source)
+ local set = {}
+ for _, l in ipairs(source) do set[l] = true end
+ return set
+end
+
+---------------------------
+-- More helper functions --
+---------------------------
+
+function busy_wait(seconds)
+ local target = mp.get_time() + seconds
+ local cycles = 0
+ while target > mp.get_time() do
+ cycles = cycles + 1
+ end
+ return cycles
+end
+
+-- Removes all keys from a table, without destroying the reference to it
+function clear_table(target)
+ for key, value in pairs(target) do
+ target[key] = nil
+ end
+end
+function shallow_copy(target)
+ if type(target) == "table" then
+ local copy = {}
+ for k, v in pairs(target) do
+ copy[k] = v
+ end
+ return copy
+ else
+ return target
+ end
+end
+
+function deep_copy(target)
+ local copy = {}
+ for k, v in pairs(target) do
+ if type(v) == "table" then
+ copy[k] = deep_copy(v)
+ else
+ copy[k] = v
+ end
+ end
+ return copy
+end
+
+-- Rounds to given decimals. eg. round_dec(3.145, 0) => 3
+function round_dec(num, idp)
+ local mult = 10^(idp or 0)
+ return math.floor(num * mult + 0.5) / mult
+end
+
+function file_exists(name)
+ local f = io.open(name, "rb")
+ if f ~= nil then
+ local ok, err, code = f:read(1)
+ io.close(f)
+ return code == nil
+ else
+ return false
+ end
+end
+
+function path_exists(name)
+ local f = io.open(name, "rb")
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+function create_directories(path)
+ local cmd
+ if ON_WINDOWS then
+ cmd = { args = {'cmd', '/c', 'mkdir', path} }
+ else
+ cmd = { args = {'mkdir', '-p', path} }
+ end
+ utils.subprocess(cmd)
+end
+
+function move_file(source_path, target_path)
+ local cmd
+ if ON_WINDOWS then
+ cmd = { cancellable=false, args = {'cmd', '/c', 'move', '/Y', source_path, target_path } }
+ utils.subprocess(cmd)
+ else
+ -- cmd = { cancellable=false, args = {'mv', source_path, target_path } }
+ os.rename(source_path, target_path)
+ end
+end
+
+function check_pid(pid)
+ -- Checks if a PID exists and returns true if so
+ local cmd, r
+ if ON_WINDOWS then
+ cmd = { cancellable=false, args = {
+ 'tasklist', '/FI', ('PID eq %d'):format(pid)
+ }}
+ r = utils.subprocess(cmd)
+ return r.stdout:sub(1,1) == '\13'
+ else
+ cmd = { cancellable=false, args = {
+ 'sh', '-c', ('kill -0 %d 2>/dev/null'):format(pid)
+ }}
+ r = utils.subprocess(cmd)
+ return r.status == 0
+ end
+end
+
+function kill_pid(pid)
+ local cmd, r
+ if ON_WINDOWS then
+ cmd = { cancellable=false, args = {'taskkill', '/F', '/PID', tostring(pid) } }
+ else
+ cmd = { cancellable=false, args = {'kill', tostring(pid) } }
+ end
+ r = utils.subprocess(cmd)
+ return r.status == 0, r
+end
+
+
+-- Find an executable in PATH or CWD with the given name
+function find_executable(name)
+ local delim = ON_WINDOWS and ";" or ":"
+
+ local pwd = os.getenv("PWD") or utils.getcwd()
+ local path = os.getenv("PATH")
+
+ local env_path = pwd .. delim .. path -- Check CWD first
+
+ local result, filename
+ for path_dir in env_path:gmatch("[^"..delim.."]+") do
+ filename = path_utils.join(path_dir, name)
+ if file_exists(filename) then
+ result = filename
+ break
+ end
+ end
+
+ return result
+end
+
+local ExecutableFinder = { path_cache = {} }
+-- Searches for an executable and caches the result if any
+function ExecutableFinder:get_executable_path( name, raw_name )
+ name = ON_WINDOWS and not raw_name and (name .. ".exe") or name
+
+ if self.path_cache[name] == nil then
+ self.path_cache[name] = find_executable(name) or false
+ end
+ return self.path_cache[name]
+end
+
+-- Format seconds to HH.MM.SS.sss
+function format_time(seconds, sep, decimals)
+ decimals = decimals == nil and 3 or decimals
+ sep = sep and sep or ":"
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals)
+
+ return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s)
+end
+
+-- Format seconds to 1h 2m 3.4s
+function format_time_hms(seconds, sep, decimals, force_full)
+ decimals = decimals == nil and 1 or decimals
+ sep = sep ~= nil and sep or " "
+
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ if force_full or h > 0 then
+ return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s)
+ elseif m > 0 then
+ return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s)
+ else
+ return string.format("%." .. tostring(decimals) .. "fs", s)
+ end
+end
+
+-- Writes text on OSD and console
+function log_info(txt, timeout)
+ timeout = timeout or 1.5
+ msg.info(txt)
+ mp.osd_message(txt, timeout)
+end
+
+-- Join table items, ala ({"a", "b", "c"}, "=", "-", ", ") => "=a-, =b-, =c-"
+function join_table(source, before, after, sep)
+ before = before or ""
+ after = after or ""
+ sep = sep or ", "
+ local result = ""
+ for i, v in pairs(source) do
+ if not isempty(v) then
+ local part = before .. v .. after
+ if i == 1 then
+ result = part
+ else
+ result = result .. sep .. part
+ end
+ end
+ end
+ return result
+end
+
+function wrap(s, char)
+ char = char or "'"
+ return char .. s .. char
+end
+-- Wraps given string into 'string' and escapes any 's in it
+function escape_and_wrap(s, char, replacement)
+ char = char or "'"
+ replacement = replacement or "\\" .. char
+ return wrap(string.gsub(s, char, replacement), char)
+end
+-- Escapes single quotes in a string and wraps the input in single quotes
+function escape_single_bash(s)
+ return escape_and_wrap(s, "'", "'\\''")
+end
+
+-- Returns (a .. b) if b is not empty or nil
+function joined_or_nil(a, b)
+ return not isempty(b) and (a .. b) or nil
+end
+
+-- Put items from one table into another
+function extend_table(target, source)
+ for i, v in pairs(source) do
+ table.insert(target, v)
+ end
+end
+
+-- Creates a handle and filename for a temporary random file (in current directory)
+function create_temporary_file(base, mode, suffix)
+ local handle, filename
+ suffix = suffix or ""
+ while true do
+ filename = base .. tostring(math.random(1, 5000)) .. suffix
+ handle = io.open(filename, "r")
+ if not handle then
+ handle = io.open(filename, mode)
+ break
+ end
+ io.close(handle)
+ end
+ return handle, filename
+end
+
+
+function get_processor_count()
+ local proc_count
+
+ if ON_WINDOWS then
+ proc_count = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
+ else
+ local cpuinfo_handle = io.open("/proc/cpuinfo")
+ if cpuinfo_handle ~= nil then
+ local cpuinfo_contents = cpuinfo_handle:read("*a")
+ local _, replace_count = cpuinfo_contents:gsub('processor', '')
+ proc_count = replace_count
+ end
+ end
+
+ if proc_count and proc_count > 0 then
+ return proc_count
+ else
+ return nil
+ end
+end
+
+function substitute_values(string, values)
+ local substitutor = function(match)
+ if match == "%" then
+ return "%"
+ else
+ -- nil is discarded by gsub
+ return values[match]
+ end
+ end
+
+ local substituted = string:gsub('%%(.)', substitutor)
+ return substituted
+end
+
+-- ASS HELPERS --
+function round_rect_top( ass, x0, y0, x1, y1, r )
+ local c = 0.551915024494 * r -- circle approximation
+ ass:move_to(x0 + r, y0)
+ ass:line_to(x1 - r, y0) -- top line
+ if r > 0 then
+ ass:bezier_curve(x1 - r + c, y0, x1, y0 + r - c, x1, y0 + r) -- top right corner
+ end
+ ass:line_to(x1, y1) -- right line
+ ass:line_to(x0, y1) -- bottom line
+ ass:line_to(x0, y0 + r) -- left line
+ if r > 0 then
+ ass:bezier_curve(x0, y0 + r - c, x0 + r - c, y0, x0 + r, y0) -- top left corner
+ end
+end
+
+function round_rect(ass, x0, y0, x1, y1, rtl, rtr, rbr, rbl)
+ local c = 0.551915024494
+ ass:move_to(x0 + rtl, y0)
+ ass:line_to(x1 - rtr, y0) -- top line
+ if rtr > 0 then
+ ass:bezier_curve(x1 - rtr + rtr*c, y0, x1, y0 + rtr - rtr*c, x1, y0 + rtr) -- top right corner
+ end
+ ass:line_to(x1, y1 - rbr) -- right line
+ if rbr > 0 then
+ ass:bezier_curve(x1, y1 - rbr + rbr*c, x1 - rbr + rbr*c, y1, x1 - rbr, y1) -- bottom right corner
+ end
+ ass:line_to(x0 + rbl, y1) -- bottom line
+ if rbl > 0 then
+ ass:bezier_curve(x0 + rbl - rbl*c, y1, x0, y1 - rbl + rbl*c, x0, y1 - rbl) -- bottom left corner
+ end
+ ass:line_to(x0, y0 + rtl) -- left line
+ if rtl > 0 then
+ ass:bezier_curve(x0, y0 + rtl - rtl*c, x0 + rtl - rtl*c, y0, x0 + rtl, y0) -- top left corner
+ end
+end
+--[[
+ A slightly more advanced option parser for scripts.
+ It supports documenting the options, and can export an example config.
+ It also can rewrite the config file with overrides, preserving the
+ original lines and appending changes to the end, along with profiles.
+
+ Does not depend on other libs.
+]]--
+
+local OptionParser = {}
+OptionParser.__index = OptionParser
+
+setmetatable(OptionParser, {
+ __call = function (cls, ...) return cls.new(...) end
+})
+
+function OptionParser.new(identifier)
+ local self = setmetatable({}, OptionParser)
+
+ self.identifier = identifier
+ self.config_file = self:_get_config_file(identifier)
+
+ self.OVERRIDE_START = "# Script-saved overrides below this line. Edits will be lost!"
+
+ -- All the options contained, as a list
+ self.options_list = {}
+ -- All the options contained, as a table with keys. See add_option
+ self.options = {}
+
+ self.default_profile = {name = "default", values = {}, loaded={}, config_lines = {}}
+ self.profiles = {}
+
+ self.active_profile = self.default_profile
+
+ -- Recusing metatable magic to wrap self.values.key.sub_key into
+ -- self.options["key.sub_key"].value, with support for assignments as well
+ function get_value_or_mapper(key)
+ local cur_option = self.options[key]
+
+ if cur_option then
+ -- Wrap tables
+ if cur_option.type == "table" then
+ return setmetatable({}, {
+ __index = function(t, sub_key)
+ return get_value_or_mapper(key .. "." .. sub_key)
+ end,
+ __newindex = function(t, sub_key, value)
+ local sub_option = self.options[key .. "." .. sub_key]
+ if sub_option and sub_option.type ~= "table" then
+ self.active_profile.values[key .. "." .. sub_key] = value
+ end
+ end
+ })
+ else
+ return self.active_profile.values[key]
+ end
+ end
+ end
+
+ -- Same recusing metatable magic to get the .default
+ function get_default_or_mapper(key)
+ local cur_option = self.options[key]
+
+ if cur_option then
+ if cur_option.type == "table" then
+ return setmetatable({}, {
+ __index = function(t, sub_key)
+ return get_default_or_mapper(key .. "." .. sub_key)
+ end,
+ })
+ else
+ return cur_option.default
+ -- return self.active_profile.values[key]
+ end
+ end
+ end
+
+ -- Easy lookups for values and defaults
+ self.values = setmetatable({}, {
+ __index = function(t, key)
+ return get_value_or_mapper(key)
+ end,
+ __newindex = function(t, key, value)
+ local option = self.options[key]
+ if option then
+ -- option.value = value
+ self.active_profile.values[key] = value
+ end
+ end
+ })
+
+ self.defaults = setmetatable({}, {
+ __index = function(t, key)
+ return get_default_or_mapper(key)
+ end
+ })
+
+ -- Hacky way to run after the script is initialized and options (hopefully) added
+ mp.add_timeout(0, function()
+ -- Handle a '--script-opts identifier-example-config=example.conf' to save an example config to a file
+ local example_dump_filename = mp.get_opt(self.identifier .. "-example-config")
+ if example_dump_filename then
+ self:save_example_options(example_dump_filename)
+
+ end
+ local explain_config = mp.get_opt(self.identifier .. "-explain-config")
+ if explain_config then
+ self:explain_options()
+ end
+
+ if (example_dump_filename or explain_config) and mp.get_property_native("options/idle") then
+ msg.info("Exiting.")
+ mp.commandv("quit")
+ end
+ end)
+
+ return self
+end
+
+function OptionParser:activate_profile(profile_name)
+ local chosen_profile = nil
+ if profile_name then
+ for i, profile in ipairs(self.profiles) do
+ if profile.name == profile_name then
+ chosen_profile = profile
+ break
+ end
+ end
+ else
+ chosen_profile = self.default_profile
+ end
+
+ if chosen_profile then
+ self.active_profile = chosen_profile
+ end
+
+end
+
+function OptionParser:add_option(key, default, description, pad_before)
+ if self.options[key] ~= nil then
+ -- Already exists!
+ return nil
+ end
+
+ local option_index = #self.options_list + 1
+ local option_type = type(default)
+
+ -- Check if option is an array
+ if option_type == "table" then
+ if default._array then
+ option_type = "array"
+ end
+ default._array = nil
+ end
+
+ local option = {
+ index = option_index,
+ type = option_type,
+ key = key,
+ default = default,
+
+ description = description,
+ pad_before = pad_before
+ }
+
+ self.options_list[option_index] = option
+
+ -- table-options are just containers for sub-options and have no value
+ if option_type == "table" then
+ option.default = nil
+
+ -- Add sub-options
+ for i, sub_option_data in ipairs(default) do
+ local sub_key = sub_option_data[1]
+ sub_option_data[1] = key .. "." .. sub_key
+ local sub_option = self:add_option(unpack(sub_option_data))
+ end
+ end
+
+ if key then
+ self.options[key] = option
+ self.default_profile.values[option.key] = option.default
+ end
+
+ return option
+end
+
+
+function OptionParser:add_options(list_of_options)
+ for i, option_args in ipairs(list_of_options) do
+ self:add_option(unpack(option_args))
+ end
+end
+
+
+function OptionParser:restore_defaults()
+ for key, option in pairs(self.options) do
+ if option.type ~= "table" then
+ self.active_profile.values[option.key] = option.default
+ end
+ end
+end
+
+function OptionParser:restore_loaded()
+ for key, option in pairs(self.options) do
+ if option.type ~= "table" then
+ -- Non-default profiles will have an .loaded entry for all options
+ local value = self.active_profile.loaded[option.key]
+ if value == nil then value = option.default end
+ self.active_profile.values[option.key] = value
+ end
+ end
+end
+
+
+function OptionParser:_get_config_file(identifier)
+ local config_filename = "script-opts/" .. identifier .. ".conf"
+ local config_file = mp.find_config_file(config_filename)
+
+ if not config_file then
+ config_filename = "lua-settings/" .. identifier .. ".conf"
+ config_file = mp.find_config_file(config_filename)
+
+ if config_file then
+ msg.warn("lua-settings/ is deprecated, use directory script-opts/")
+ end
+ end
+
+ return config_file
+end
+
+
+function OptionParser:value_to_string(value)
+ if type(value) == "boolean" then
+ if value then value = "yes" else value = "no" end
+ elseif type(value) == "table" then
+ return utils.format_json(value)
+ end
+ return tostring(value)
+end
+
+
+function OptionParser:string_to_value(option_type, value)
+ if option_type == "boolean" then
+ if value == "yes" or value == "true" then
+ value = true
+ elseif value == "no" or value == "false" then
+ value = false
+ else
+ -- can't parse as boolean
+ value = nil
+ end
+ elseif option_type == "number" then
+ value = tonumber(value)
+ if value == nil then
+ -- Can't parse as number
+ end
+ elseif option_type == "array" then
+ value = utils.parse_json(value)
+ end
+ return value
+end
+
+
+function OptionParser:get_profile(profile_name)
+ for i, profile in ipairs(self.profiles) do
+ if profile.name == profile_name then
+ return profile
+ end
+ end
+end
+
+
+function OptionParser:create_profile(profile_name, base_on_original)
+ if not self:get_profile(profile_name) then
+ new_profile = {name = profile_name, values={}, loaded={}, config_lines={}}
+
+ if base_on_original then
+ -- Copy values from default config
+ for k, v in pairs(self.default_profile.values) do
+ new_profile.values[k] = v
+ end
+ for k, v in pairs(self.default_profile.loaded) do
+ new_profile.loaded[k] = v
+ end
+ else
+ -- Copy current values, but not loaded
+ for k, v in pairs(self.active_profile.values) do
+ new_profile.values[k] = v
+ end
+ end
+
+ table.insert(self.profiles, new_profile)
+ return new_profile
+ end
+end
+
+
+function OptionParser:load_options()
+ if not self.config_file then return end
+ local file = io.open(self.config_file, 'r')
+ if not file then return end
+
+ local trim = function(text)
+ return (text:gsub("^%s*(.-)%s*$", "%1"))
+ end
+
+ local current_profile = self.default_profile
+ local override_reached = false
+ local line_index = 1
+
+ -- Read all lines in advance
+ local lines = {}
+ for line in file:lines() do
+ table.insert(lines, line)
+ end
+ file:close()
+
+ local total_lines = #lines
+
+ while line_index < total_lines + 1 do
+ local line = lines[line_index]
+
+ local profile_name = line:match("^%[(..-)%]$")
+
+ if line == self.OVERRIDE_START then
+ override_reached = true
+
+ elseif line:find("#") == 1 then
+ -- Skip comments
+ elseif profile_name then
+ current_profile = self:get_profile(profile_name) or self:create_profile(profile_name, true)
+ override_reached = false
+
+ else
+ local key, value = line:match("^(..-)=(.+)$")
+ if key then
+ key = trim(key)
+ value = trim(value)
+
+ local option = self.options[key]
+ if not option then
+ msg.warn(("%s:%d ignoring unknown key '%s'"):format(self.config_file, line_index, key))
+ elseif option.type == "table" then
+ msg.warn(("%s:%d ignoring value for table-option %s"):format(self.config_file, line_index, key))
+ else
+ -- If option is an array, make sure we read all lines
+ if option.type == "array" then
+ local start_index = line_index
+ -- Read lines until one ends with ]
+ while not value:match("%]%s*$") do
+ line_index = line_index + 1
+ if line_index > total_lines then
+ msg.error(("%s:%d non-ending %s for key '%s'"):format(self.config_file, start_index, option.type, key))
+ end
+ value = value .. trim(lines[line_index])
+ end
+ end
+ local parsed_value = self:string_to_value(option.type, value)
+
+ if parsed_value == nil then
+ msg.error(("%s:%d error parsing value '%s' for key '%s' (as %s)"):format(self.config_file, line_index, value, key, option.type))
+ else
+ current_profile.values[option.key] = parsed_value
+ if not override_reached then
+ current_profile.loaded[option.key] = parsed_value
+ end
+ end
+ end
+ end
+ end
+
+ if not override_reached and not profile_name then
+ table.insert(current_profile.config_lines, line)
+ end
+
+ line_index = line_index + 1
+ end
+end
+
+
+function OptionParser:save_options()
+ if not self.config_file then return nil, "no configuration file found" end
+
+ local file = io.open(self.config_file, 'w')
+ if not file then return nil, "unable to open configuration file for writing" end
+
+ local profiles = {self.default_profile}
+ for i, profile in ipairs(self.profiles) do
+ table.insert(profiles, profile)
+ end
+
+ local out_lines = {}
+
+ local add_linebreak = function()
+ if out_lines[#out_lines] ~= '' then
+ table.insert(out_lines, '')
+ end
+ end
+
+ for profile_index, profile in ipairs(profiles) do
+
+ local profile_override_lines = {}
+ for option_index, option in ipairs(self.options_list) do
+ local option_value = profile.values[option.key]
+ local option_loaded = profile.loaded[option.key]
+
+ if option_loaded == nil then
+ option_loaded = self.default_profile.loaded[option.key]
+ end
+ if option_loaded == nil then
+ option_loaded = option.default
+ end
+
+ -- If value is different from default AND loaded value, store it in array
+ if option.key then
+ if (option_value ~= option_loaded) then
+ table.insert(profile_override_lines, ('%s=%s'):format(option.key, self:value_to_string(option_value)))
+ end
+ end
+ end
+
+ if (#profile.config_lines > 0 or #profile_override_lines > 0) and profile ~= self.default_profile then
+ -- Write profile name, if this is not default profile
+ add_linebreak()
+ table.insert(out_lines, ("[%s]"):format(profile.name))
+ end
+
+ -- Write original config lines
+ for line_index, line in ipairs(profile.config_lines) do
+ table.insert(out_lines, line)
+ end
+ -- end
+
+ if #profile_override_lines > 0 then
+ -- Add another newline before the override comment, if needed
+ add_linebreak()
+
+ table.insert(out_lines, self.OVERRIDE_START)
+ for override_line_index, override_line in ipairs(profile_override_lines) do
+ table.insert(out_lines, override_line)
+ end
+ end
+
+ end
+
+ -- Add a final linebreak if needed
+ add_linebreak()
+
+ file:write(table.concat(out_lines, "\n"))
+ file:close()
+
+ return true
+end
+
+
+function OptionParser:get_default_config_lines()
+ local example_config_lines = {}
+
+ for option_index, option in ipairs(self.options_list) do
+ if option.pad_before then
+ table.insert(example_config_lines, '')
+ end
+
+ if option.description then
+ for description_line in option.description:gmatch('[^\r\n]+') do
+ table.insert(example_config_lines, ('# ' .. description_line))
+ end
+ end
+ if option.key and option.type ~= "table" then
+ table.insert(example_config_lines, ('%s=%s'):format(option.key, self:value_to_string(option.default)) )
+ end
+ end
+ return example_config_lines
+end
+
+
+function OptionParser:explain_options()
+ local example_config_lines = self:get_default_config_lines()
+ msg.info(table.concat(example_config_lines, '\n'))
+end
+
+
+function OptionParser:save_example_options(filename)
+ local file = io.open(filename, "w")
+ if not file then
+ msg.error("Unable to open file '" .. filename .. "' for writing")
+ else
+ local example_config_lines = self:get_default_config_lines()
+ file:write(table.concat(example_config_lines, '\n'))
+ file:close()
+ msg.info("Wrote example config to file '" .. filename .. "'")
+ end
+end
+local SCRIPT_NAME = "mpv_crop_script"
+
+local SCRIPT_KEYBIND = "c"
+local SCRIPT_HANDLER = "crop-screenshot"
+
+--------------------
+-- Script options --
+--------------------
+
+local script_options = OptionParser(SCRIPT_NAME)
+local option_values = script_options.values
+
+script_options:add_options({
+ {nil, nil, "mpv_crop_script.lua options and default values"},
+ {nil, nil, "Output options #", true},
+ {"output_template", "${filename}${!is_image: ${#pos:%02h.%02m.%06.3s}}${!full: ${crop_w}x${crop_h}} ${%unique:%03d}.${ext}",
+ "Filename output template. See README.md for property expansion documentation."},
+ {nil, nil, [[Script-provided properties:
+ filename - filename without extension
+ file_ext - original extension without leading dot
+ path - original file path
+ pos - playback time
+ ext - output file extension without leading dot
+ crop_w - crop width
+ crop_h - crop height
+ crop_x - left
+ crop_y - top
+ crop_x2 - right
+ crop_y2 - bottom
+ full - boolean denoting a full (temporary) screenshot instead of crop
+ is_image - boolean denoting the source file is likely an image (zero duration and position)
+ unique - counter that will increase per each existing filename, until a unique name is found]]},
+
+ {"output_format", "png",
+ "Format (encoder) to save final crops in. For example, png, mjpeg, targa, bmp"},
+ {"output_extension", "",
+ "Output extension. Leave blank to try to choose from the encoder (if supported)"},
+
+ {"create_directories", false,
+ "Whether to create the directories in the final output path (defined by output_template)"},
+ {"skip_screenshot_for_images", true,
+ "If the current file is an image, skip taking a temporary screenshot and crop the image directly"},
+ {"keep_original", false,
+ "Keep the full-sized temporary screenshot as well"},
+
+ {nil, nil, "Crop tool options #", true},
+ {"overlay_transparency", 160,
+ "Transparency (0 - opaque, 255 - transparent) of the dim overlay on the non-cropped area"},
+ {"overlay_lightness", 0,
+ "Ligthness (0 - black, 255 - white) of the dim overlay on the non-cropped area"},
+ {"draw_mouse", false,
+ "Draw the crop crosshair"},
+ {"guide_type", "none",
+ "Crop guide type. One of: none, grid, center"},
+ {"color_invert", false,
+ "Use black lines instead of white for the crop frame and crosshair"},
+ {"auto_invert", false,
+ "Try to check if video is light or dark upon opening crop tool, and invert the colors if necessary"},
+
+ {nil, nil, "Misc options #", true},
+ {"warn_about_template", true,
+ "Warn about output_template missing ${ext}, to ensure the extension is not missing"},
+ {"disable_keybind", false,
+ "Disable the built-in keybind"}
+})
+
+-- Read user-given options, if any
+script_options:load_options()
+--[[
+ DisplayState keeps track of the current display state, and can
+ handle mapping between video-space coords and display-space coords.
+ Handles panscan and offsets and aligns and all that, following what
+ mpv itself does (video/out/aspect.c).
+
+ Does not depend on other libs.
+]]--
+
+local DisplayState = {}
+DisplayState.__index = DisplayState
+
+setmetatable(DisplayState, {
+ __call = function (cls, ...) return cls.new(...) end
+})
+
+function DisplayState.new()
+ local self = setmetatable({}, DisplayState)
+
+ self:reset()
+
+ return self
+end
+
+function DisplayState:reset()
+ self.screen = {} -- Display (window, fullscreen) size
+ self.video = {} -- Video size
+ self.scale = {} -- video / screen
+ self.bounds = {} -- Video rect within display
+
+ self.screen_ready = false
+ self.video_ready = false
+
+ -- Stores internal display state (panscan, align, zoom etc)
+ self.current_state = nil
+end
+
+function DisplayState:setup_events()
+ mp.register_event("file-loaded", function() self:event_file_loaded() end)
+end
+
+function DisplayState:event_file_loaded()
+ self:reset()
+ self:recalculate_bounds(true)
+end
+
+-- Turns screen-space XY to video XY (can go negative)
+function DisplayState:screen_to_video(x, y)
+ local nx = (x - self.bounds.left) * self.scale.x
+ local ny = (y - self.bounds.top ) * self.scale.y
+ return nx, ny
+end
+
+-- Turns video-space XY to screen XY
+function DisplayState:video_to_screen(x, y)
+ local nx = (x / self.scale.x) + self.bounds.left
+ local ny = (y / self.scale.y) + self.bounds.top
+ return nx, ny
+end
+
+function DisplayState:_collect_display_state()
+ local screen_w, screen_h, screen_aspect = mp.get_osd_size()
+
+ local state = {
+ screen_w = screen_w,
+ screen_h = screen_h,
+ screen_aspect = screen_aspect,
+
+ video_w = mp.get_property_native("dwidth"),
+ video_h = mp.get_property_native("dheight"),
+
+ video_w_raw = mp.get_property_native("video-out-params/w"),
+ video_h_raw = mp.get_property_native("video-out-params/h"),
+
+ panscan = mp.get_property_native("panscan"),
+ video_zoom = mp.get_property_native("video-zoom"),
+ video_unscaled = mp.get_property_native("video-unscaled"),
+
+ video_align_x = mp.get_property_native("video-align-x"),
+ video_align_y = mp.get_property_native("video-align-y"),
+
+ video_pan_x = mp.get_property_native("video-pan-x"),
+ video_pan_y = mp.get_property_native("video-pan-y"),
+
+ fullscreen = mp.get_property_native("fullscreen"),
+ keepaspect = mp.get_property_native("keepaspect"),
+ keepaspect_window = mp.get_property_native("keepaspect-window")
+ }
+
+ return state
+end
+
+function DisplayState:_state_changed(state)
+ if self.current_state == nil then return true end
+
+ for k in pairs(state) do
+ if state[k] ~= self.current_state[k] then return true end
+ end
+ return false
+end
+
+
+function DisplayState:recalculate_bounds(forced)
+ local new_state = self:_collect_display_state()
+ if not (forced or self:_state_changed(new_state)) then
+ -- Early out
+ return self.screen_ready
+ end
+ self.current_state = new_state
+
+ -- Store screen dimensions
+ self.screen.width = new_state.screen_w
+ self.screen.height = new_state.screen_h
+ self.screen.ratio = new_state.screen_w / new_state.screen_h
+ self.screen_ready = true
+
+ -- Video dimensions
+ if new_state.video_w and new_state.video_h then
+ self.video.width = new_state.video_w
+ self.video.height = new_state.video_h
+ self.video.ratio = new_state.video_w / new_state.video_h
+
+ -- This magic has been adapted from mpv's own video/out/aspect.c
+
+ if new_state.keepaspect then
+ local scaled_w, scaled_h = self:_aspect_calc_panscan(new_state)
+ local video_left, video_right = self:_split_scaling(new_state.screen_w, scaled_w, new_state.video_zoom, new_state.video_align_x, new_state.video_pan_x)
+ local video_top, video_bottom = self:_split_scaling(new_state.screen_h, scaled_h, new_state.video_zoom, new_state.video_align_y, new_state.video_pan_y)
+ self.bounds = {
+ left = video_left,
+ right = video_right,
+
+ top = video_top,
+ bottom = video_bottom,
+
+ width = video_right - video_left,
+ height = video_bottom - video_top,
+ }
+ else
+ self.bounds = {
+ left = 0,
+ top = 0,
+ right = self.screen.width,
+ bottom = self.screen.height,
+
+ width = self.screen.width,
+ height = self.screen.height,
+ }
+ end
+
+ self.scale.x = new_state.video_w_raw / self.bounds.width
+ self.scale.y = new_state.video_h_raw / self.bounds.height
+
+ self.video_ready = true
+ end
+
+ return self.screen_ready
+end
+
+
+function DisplayState:_aspect_calc_panscan(state)
+ -- From video/out/aspect.c
+ local f_width = state.screen_w
+ local f_height = (state.screen_w / state.video_w) * state.video_h
+
+ if f_height > state.screen_h or f_height < state.video_h_raw then
+ local tmp_w = (state.screen_h / state.video_h) * state.video_w
+ if tmp_w <= state.screen_w then
+ f_height = state.screen_h
+ f_width = tmp_w
+ end
+ end
+
+ local vo_panscan_area = state.screen_h - f_height
+
+ local f_w = f_width / f_height
+ local f_h = 1
+ if (vo_panscan_area == 0) then
+ vo_panscan_area = state.screen_w - f_width
+ f_w = 1
+ f_h = f_height / f_width
+ end
+
+ if state.video_unscaled then
+ vo_panscan_area = 0
+ if state.video_unscaled ~= "downscale-big" or ((state.video_w <= state.screen_w) and (state.video_h <= state.screen_h)) then
+ f_width = state.video_w
+ f_height = state.video_h
+ end
+ end
+
+ local scaled_w = math.floor( f_width + vo_panscan_area * state.panscan * f_w )
+ local scaled_h = math.floor( f_height + vo_panscan_area * state.panscan * f_h )
+ return scaled_w, scaled_h
+end
+
+function DisplayState:_split_scaling(dst_size, scaled_src_size, zoom, align, pan)
+ -- From video/out/aspect.c as well
+ scaled_src_size = math.floor(scaled_src_size * 2^zoom)
+ align = (align + 1) / 2
+
+ local dst_start = (dst_size - scaled_src_size) * align + pan * scaled_src_size
+ local dst_end = dst_start + scaled_src_size
+
+ -- We don't actually want these - we want to go out of bounds!
+ -- dst_start = math.max(0, dst_start)
+ -- dst_end = math.min(dst_size, dst_end)
+
+ return math.floor(dst_start), math.floor(dst_end)
+end
+--[[
+ ASSCropper is a tool to get crop values with a visual tool
+ that handles mouse clicks and drags to manipulate a crop box,
+ with a crosshair, guides, etc.
+
+ Indirectly depends on DisplayState (as a given instance).
+]]--
+
+local ASSCropper = {}
+ASSCropper.__index = ASSCropper
+
+setmetatable(ASSCropper, {
+ __call = function (cls, ...) return cls.new(...) end
+})
+
+function ASSCropper.new(display_state)
+ local self = setmetatable({}, ASSCropper)
+ local script_name = mp.get_script_name()
+ self.keybind_group = script_name .. "_asscropper_binds"
+ self.cropdetect_label = script_name .. "_asscropper_cropdetect"
+ self.blackframe_label = script_name .. "_asscropper_blackframe"
+ self.crop_label = script_name .. "_asscropper_crop"
+
+ self.display_state = display_state
+
+ self.tick_callback = nil
+ self.tick_timer = mp.add_periodic_timer(1/60, function()
+ if self.tick_callback then self.tick_callback() end
+ end)
+ self.tick_timer:stop()
+
+ self.text_size = 18
+
+ self.overlay_transparency = 160
+ self.overlay_lightness = 0
+
+ self.corner_size = 40
+ self.corner_required_size = self.corner_size * 3
+
+ self.guide_type_names = {
+ [0] = "No guides",
+ [1] = "Grid guides",
+ [2] = "Center guides"
+ }
+ self.guide_type_count = 3
+
+ self.default_options = {
+ even_dimensions = false,
+ guide_type = 0,
+ draw_mouse = false,
+ draw_help = true,
+ color_invert = false,
+ auto_invert = false,
+ }
+ self.options = default_options
+
+ self.active = false
+
+ self.mouse_screen = {x=0, y=0}
+ self.mouse_video = {x=0, y=0}
+
+ -- Crop in video-space
+ self.current_crop = nil
+
+ self.dragging = 0
+ self.drag_start = {x=0, y=0}
+ self.restrict_ratio = false
+
+ self.testing_crop = false
+
+ self.detecting_crop = nil
+ self.cropdetect_wait = nil
+ self.cropdetect_timeout = nil
+
+ self.detecting_blackframe = nil
+ self.blackframe_wait = nil
+ self.blackframe_timeout = nil
+
+ self.nudges = {
+ NUDGE_LEFT = {-1, 0, -1, 0},
+ NUDGE_UP = { 0, -1, 0, -1},
+ NUDGE_RIGHT = { 1, 0, 1, 0},
+ NUDGE_DOWN = { 0, 1, 0, 1}
+ }
+
+ self.resizes = {
+ SHRINK_LEFT = { 1, 0, 0, 0},
+ SHRINK_TOP = { 0, 1, 0, 0},
+ SHRINK_RIGHT = { 0, 0, -1, 0},
+ SHRINK_BOT = { 0, 0, 0, -1},
+
+ GROW_LEFT = {-1, 0, 0, 0},
+ GROW_TOP = { 0, -1, 0, 0},
+ GROW_RIGHT = { 0, 0, 1, 0},
+ GROW_BOT = { 0, 0, 0, 1},
+ }
+
+ self._key_binds = {
+ {"mouse_move", function() self:update_mouse_position() end },
+ {"mouse_btn0", function(e) self:on_mouse("mouse_btn0", e) end, {complex=true}},
+ {"shift+mouse_btn0", function(e) self:on_mouse("mouse_btn0", e, true) end, {complex=true}},
+
+ {"c", function() self:key_event("CROSSHAIR") end },
+ {"d", function() self:key_event("CROP_DETECT") end },
+ {"x", function() self:key_event("GUIDES") end },
+ {"t", function() self:key_event("TEST") end },
+ {"z", function() self:key_event("INVERT") end },
+
+ {"shift+left", function() self:key_event("NUDGE_LEFT") end, {repeatable=true} },
+ {"shift+up", function() self:key_event("NUDGE_UP") end, {repeatable=true} },
+ {"shift+right", function() self:key_event("NUDGE_RIGHT") end, {repeatable=true} },
+ {"shift+down", function() self:key_event("NUDGE_DOWN") end, {repeatable=true} },
+
+ {"ctrl+left", function() self:key_event("GROW_LEFT") end, {repeatable=true} },
+ {"ctrl+up", function() self:key_event("GROW_TOP") end, {repeatable=true} },
+ {"ctrl+right", function() self:key_event("SHRINK_LEFT") end, {repeatable=true} },
+ {"ctrl+down", function() self:key_event("SHRINK_TOP") end, {repeatable=true} },
+
+ {"ctrl+shift+left", function() self:key_event("SHRINK_RIGHT") end, {repeatable=true} },
+ {"ctrl+shift+up", function() self:key_event("SHRINK_BOT") end, {repeatable=true} },
+ {"ctrl+shift+right", function() self:key_event("GROW_RIGHT") end, {repeatable=true} },
+ {"ctrl+shift+down", function() self:key_event("GROW_BOT") end, {repeatable=true} },
+
+ {"ENTER", function() self:key_event("ENTER") end },
+ {"ESC", function() self:key_event("ESC") end }
+ }
+
+ self._keys_bound = false
+
+ for k, v in pairs(self._key_binds) do
+ -- Insert a key name into the tables
+ table.insert(v, 2, self.keybind_group .. "_key_" .. v[1])
+ end
+
+ return self
+end
+
+function ASSCropper:enable_key_bindings()
+ if not self._keys_bound then
+ for k, v in pairs(self._key_binds) do
+ mp.add_forced_key_binding(unpack(v))
+ end
+ -- Clear "allow-vo-dragging"
+ mp.input_enable_section("input_forced_" .. mp.script_name)
+ self._keys_bound = true
+ end
+end
+
+function ASSCropper:disable_key_bindings()
+ for k, v in pairs(self._key_binds) do
+ mp.remove_key_binding(v[2]) -- remove by name
+ end
+ self._keys_bound = false
+end
+
+
+function ASSCropper:finalize_crop()
+ if self.current_crop ~= nil then
+ local x1, x2 = self.current_crop[1].x, self.current_crop[2].x
+ local y1, y2 = self.current_crop[1].y, self.current_crop[2].y
+
+ self.current_crop.x, self.current_crop.y = x1, y1
+ self.current_crop.w, self.current_crop.h = x2 - x1, y2 - y1
+
+ if self.options.even_dimensions then
+ self.current_crop.w = self.current_crop.w - (self.current_crop.w % 2)
+ self.current_crop.h = self.current_crop.h - (self.current_crop.h % 2)
+ end
+
+ self.current_crop.x1, self.current_crop.x2 = x1, x1 + self.current_crop.w
+ self.current_crop.y1, self.current_crop.y2 = y1, y1 + self.current_crop.h
+
+ self.current_crop[2].x, self.current_crop[2].y = self.current_crop.x2, self.current_crop.y2
+ end
+end
+
+
+function ASSCropper:key_event(name)
+ if name == "ENTER" then
+ self:stop_crop(false)
+
+ self:finalize_crop()
+
+ if self.callback_on_crop == nil then
+ mp.set_osd_ass(0,0, "")
+ else
+ self.callback_on_crop(self.current_crop)
+ end
+
+ elseif name == "ESC" then
+ self:stop_crop(true)
+
+ if self.callback_on_cancel == nil then
+ mp.set_osd_ass(0,0, "")
+ else
+ self.callback_on_cancel()
+ end
+
+ elseif name == "TEST" then
+ self:toggle_testing()
+
+ elseif not self.testing_crop then
+ if name == "CROP_DETECT" then
+ self:toggle_crop_detect()
+
+ elseif name == "CROSSHAIR" then
+ self.options.draw_mouse = not self.options.draw_mouse;
+ elseif name == "INVERT" then
+ self.options.color_invert = not self.options.color_invert;
+ elseif name == "GUIDES" then
+ self.options.guide_type = (self.options.guide_type + 1) % (self.guide_type_count)
+ mp.osd_message(self.guide_type_names[self.options.guide_type])
+ elseif self.nudges[name] then
+ self:nudge(true, unpack(self.nudges[name]))
+ elseif self.resizes[name] then
+ self:nudge(false, unpack(self.resizes[name]))
+ end
+ end
+end
+
+function ASSCropper:nudge(keep_size, left, top, right, bottom)
+ if self.current_crop == nil then return end
+
+ local x1, y1 = self.current_crop[1].x, self.current_crop[1].y
+ local x2, y2 = self.current_crop[2].x, self.current_crop[2].y
+
+ local w, h = x2 - x1, y2 - y1
+ if not keep_size then
+ w, h = 0, 0
+
+ if self.options.even_dimensions then
+ left = left * 2
+ top = top * 2
+ right = right * 2
+ bottom = bottom * 2
+ end
+
+ end
+
+ local vw, vh = self.display_state.video.width, self.display_state.video.height
+
+ x1 = math.max(0, math.min(vw-w, x1 + left))
+ y1 = math.max(0, math.min(vh-h, y1 + top))
+
+ x2 = math.max(w, math.min(vw, x2 + right))
+ y2 = math.max(h, math.min(vh, y2 + bottom))
+
+ local x_offset = math.max(0, 0-x1) - math.max(0, x2-vw)
+ local y_offset = math.max(0, 0-y1) - math.max(0, y2-vh)
+
+ x1 = x1 + x_offset
+ y1 = y1 + y_offset
+ x2 = x2 + x_offset
+ y2 = y2 + y_offset
+
+ self.current_crop[1].x, self.current_crop[2].x = order_pair(x1, x2)
+ self.current_crop[1].y, self.current_crop[2].y = order_pair(y1, y2)
+end
+
+function ASSCropper:blackframe_stop()
+ if self.detecting_blackframe then
+ self.detecting_blackframe:stop()
+ self.detecting_blackframe = nil
+
+ local filters = mp.get_property_native("vf")
+ for i, filter in ipairs(filters) do
+ if filter.label == self.blackframe_label then
+ table.remove(filters, i)
+ end
+ end
+ mp.set_property_native("vf", filters)
+ end
+
+end
+
+function ASSCropper:toggle_testing()
+ if self.testing_crop then
+ self:stop_testing()
+ else
+ self:start_testing()
+ end
+end
+
+function ASSCropper:start_testing()
+ if not self.testing_crop then
+
+ local cw = self.current_crop and (self.current_crop[2].x - self.current_crop[1].x) or 0
+ local ch = self.current_crop and (self.current_crop[2].y - self.current_crop[1].y) or 0
+
+ if cw == 0 or ch == 0 then
+ return mp.osd_message("Can't test current crop")
+ end
+
+ self:cropdetect_stop()
+ self:blackframe_stop()
+
+ local crop_filter = ('@%s:crop=w=%d:h=%d:x=%d:y=%d'):format(
+ self.crop_label, cw, ch, self.current_crop[1].x, self.current_crop[1].y
+ )
+ local ret = mp.commandv('vf', 'add', crop_filter)
+ if ret then
+ self.testing_crop = true
+ end
+ end
+end
+
+function ASSCropper:stop_testing()
+ if self.testing_crop then
+ local filters = mp.get_property_native("vf")
+ for i, filter in ipairs(filters) do
+ if filter.label == self.crop_label then
+ table.remove(filters, i)
+ end
+ end
+ mp.set_property_native("vf", filters)
+ self.testing_crop = false
+ end
+end
+
+
+function ASSCropper:blackframe_check()
+ local blackframe_metadata = mp.get_property_native("vf-metadata/" .. self.blackframe_label)
+ local black_percentage = tonumber(blackframe_metadata["lavfi.blackframe.pblack"])
+
+ local now = mp.get_time()
+ if black_percentage ~= nil and now >= self.blackframe_wait then
+ self:blackframe_stop()
+
+ self.options.color_invert = black_percentage < 50
+ elseif now > self.blackframe_timeout then
+ -- Couldn't get blackframe metadata in time!
+ self:blackframe_stop()
+ end
+end
+
+function ASSCropper:blackframe_start()
+ self:blackframe_stop()
+ if not self.detecting_blackframe then
+
+ local blackframe_filter = ('@%s:blackframe=amount=%d:threshold=%d'):format(self.blackframe_label, 0, 128)
+
+ local ret = mp.commandv('vf', 'add', blackframe_filter)
+ if ret then
+ self.blackframe_wait = mp.get_time() + 0.15
+ self.blackframe_timeout = self.blackframe_wait + 1
+
+ self.detecting_blackframe = mp.add_periodic_timer(1/10, function()
+ self:blackframe_check()
+ end)
+ end
+ end
+end
+
+function ASSCropper:cropdetect_stop()
+ if self.detecting_crop then
+ self.detecting_crop:stop()
+ self.detecting_crop = nil
+ self.cropdetect_wait = nil
+ self.cropdetect_timeout = nil
+
+ local filters = mp.get_property_native("vf")
+ for i, filter in ipairs(filters) do
+ if filter.label == self.cropdetect_label then
+ table.remove(filters, i)
+ end
+ end
+ mp.set_property_native("vf", filters)
+ end
+
+end
+
+function ASSCropper:cropdetect_check()
+ local cropdetect_metadata = mp.get_property_native("vf-metadata/" .. self.cropdetect_label)
+ local get_n = function(s) return tonumber(cropdetect_metadata["lavfi.cropdetect." .. s]) end
+
+ local now = mp.get_time()
+ if not isempty(cropdetect_metadata) and now >= self.cropdetect_wait then
+ self:cropdetect_stop()
+
+ self.current_crop = {
+ {x=get_n("x1"), y=get_n("y1")},
+ {x=get_n("x2")+1, y=get_n("y2")+1},
+ }
+
+ mp.osd_message("Crop detected")
+ elseif now > self.cropdetect_timeout then
+ mp.osd_message("Crop detect timed out")
+ self:cropdetect_stop()
+ end
+end
+
+function ASSCropper:toggle_crop_detect()
+ if self.detecting_crop then
+ self:cropdetect_stop()
+ mp.osd_message("Cancelled crop detect")
+
+ else
+ local cropdetect_filter = ('@%s:cropdetect=limit=%f:round=2:reset=0'):format(self.cropdetect_label, 30/255)
+
+ local ret = mp.commandv('vf', 'add', cropdetect_filter)
+ if not ret then
+ mp.osd_message("Crop detect failed")
+ else
+ self.cropdetect_wait = mp.get_time() + 0.2
+ self.cropdetect_timeout = self.cropdetect_wait + 1.5
+
+ mp.osd_message("Starting automatic crop detect")
+ self.detecting_crop = mp.add_periodic_timer(1/10, function()
+ self:cropdetect_check()
+ end)
+ end
+ end
+end
+
+
+function ASSCropper:start_crop(options, on_crop, on_cancel)
+ -- Refresh display state
+ self.display_state:recalculate_bounds(true)
+ if self.display_state.video_ready then
+ self.active = true
+ self.tick_timer:resume()
+
+ self.options = {}
+
+ for k, v in pairs(self.default_options) do
+ self.options[k] = v
+ end
+ for k, v in pairs(options or {}) do
+ self.options[k] = v
+ end
+
+ self.callback_on_crop = on_crop
+ self.callback_on_cancel = on_cancel
+
+ self.dragging = 0
+
+ self:enable_key_bindings()
+ self:update_mouse_position()
+
+ if self.options.auto_invert then
+ self:blackframe_start()
+ end
+ end
+end
+
+function ASSCropper:stop_crop(clear)
+ self.active = false
+ self.tick_timer:stop()
+
+ self:cropdetect_stop()
+ self:blackframe_stop()
+ self:stop_testing()
+
+ self:disable_key_bindings()
+ if clear then
+ self.current_crop = nil
+ end
+end
+
+
+function ASSCropper:on_tick()
+ -- Unused, for debugging
+ if self.active then
+ self.display_state:recalculate_bounds()
+ self:render()
+ end
+end
+
+
+function ASSCropper:update_mouse_position()
+ -- These are real on-screen coords.
+ self.mouse_screen.x, self.mouse_screen.y = mp.get_mouse_pos()
+
+ if self.display_state:recalculate_bounds() and self.display_state.video_ready then
+ -- These are on-video coords.
+ local mx, my = self.display_state:screen_to_video(self.mouse_screen.x, self.mouse_screen.y)
+ self.mouse_video.x = mx
+ self.mouse_video.y = my
+ end
+
+end
+
+
+function ASSCropper:get_hitboxes(crop_box)
+ crop_box = crop_box or self.current_crop
+ if crop_box == nil then
+ return nil
+ end
+
+ local x1, x2 = order_pair(crop_box[1].x, crop_box[2].x)
+ local y1, y2 = order_pair(crop_box[1].y, crop_box[2].y)
+ local w, h = math.abs(x2 - x1), math.abs(y2 - y1)
+
+ -- Corner and required corner size in videospace pixels
+ local mult = math.min(self.display_state.scale.x, self.display_state.scale.y)
+ local videospace_corner_size = self.corner_size * mult
+ local videospace_required_size = self.corner_required_size * mult
+
+ local handles_outside = (math.min(w, h) <= videospace_required_size)
+
+ local hitbox_bases = {
+ { x1, y2, x1, y2 }, -- BL
+ { x1, y2, x2, y2 }, -- B
+ { x2, y2, x2, y2 }, -- BR
+
+ { x1, y1, x1, y2 }, -- L
+ { x1, y1, x2, y2 }, -- Center
+ { x2, y1, x2, y2 }, -- R
+
+ { x1, y1, x1, y1 }, -- TL
+ { x1, y1, x2, y1 }, -- T
+ { x2, y1, x2, y1 } -- TR
+ }
+
+ local hitbox_mults
+ if handles_outside then
+ hitbox_mults = {
+ {-1, 0, 0, 1},
+ { 0, 0, 0, 1},
+ { 0, 0, 1, 1},
+
+ {-1, 0, 0, 0},
+ { 0, 0, 0, 0},
+ { 0, 0, 1, 0},
+
+ {-1, -1, 0, 0},
+ { 0, -1, 0, 0},
+ { 0, -1, 1, 0}
+ }
+
+ else
+ hitbox_mults = {
+ { 0, -1, 1, 0},
+ { 1, -1, -1, 0},
+ {-1, -1, 0, 0},
+
+ { 0, 1, 1, -1},
+ { 1, 1, -1, -1},
+ {-1, 1, 0, -1},
+
+ { 0, 0, 1, 1},
+ { 1, 0, -1, 1},
+ {-1, 0, 0, 1}
+ }
+ end
+
+
+ local hitboxes = {}
+ for index, hitbox_base in ipairs(hitbox_bases) do
+ local hitbox_mult = hitbox_mults[index]
+
+ hitboxes[index] = {
+ hitbox_base[1] + hitbox_mult[1] * videospace_corner_size,
+ hitbox_base[2] + hitbox_mult[2] * videospace_corner_size,
+ hitbox_base[3] + hitbox_mult[3] * videospace_corner_size,
+ hitbox_base[4] + hitbox_mult[4] * videospace_corner_size
+ }
+ end
+ -- Pseudobox to easily pass the original crop box
+ hitboxes[10] = {x1, y1, x2, y2}
+
+ return hitboxes
+end
+
+
+function ASSCropper:hit_test(hitboxes, position)
+ if hitboxes == nil then
+ return 0
+
+ else
+ local px, py = position.x, position.y
+
+ for i = 1,9 do
+ local hb = hitboxes[i]
+
+ if (px >= hb[1] and px < hb[3]) and (py >= hb[2] and py < hb[4]) then
+ return i
+ end
+
+ end
+ -- No hits
+ return 0
+ end
+end
+
+
+function ASSCropper:on_mouse(button, event, shift_down)
+ if not(event.event == "up" or event.event == "down") then return end
+ mouse_down = event.event == "down"
+ shift_down = shift_down or false
+
+ if button == "mouse_btn0" and self.active and not self.detecting_crop and not self.testing_crop then
+
+ local mouse_pos = {x=self.mouse_video.x, y=self.mouse_video.y}
+
+ -- Helpers
+ local xy_same = function(a, b) return a.x == b.x and a.y == b.y end
+ local xy_distance = function(a, b)
+ local dx = a.x - b.x
+ local dy = a.y - b.y
+ return math.sqrt( dx*dx + dy*dy )
+ end
+ --
+
+ if mouse_down then -- Mouse pressed
+
+ local bound_mouse_pos = {
+ x = math.max(0, math.min(self.display_state.video.width, mouse_pos.x)),
+ y = math.max(0, math.min(self.display_state.video.height, mouse_pos.y)),
+ }
+
+ if self.current_crop == nil then
+ self.current_crop = { bound_mouse_pos, bound_mouse_pos }
+
+ self.dragging = 3
+ self.anchor_pos = {bound_mouse_pos.x, bound_mouse_pos.y}
+
+ self.crop_ratio = 1
+ self.drag_start = bound_mouse_pos
+
+ local handle_pos = self:_get_anchor_positions()[hit]
+ self.drag_offset = {0, 0}
+
+ self.restrict_ratio = shift_down
+
+ elseif self.dragging == 0 then
+ -- Check if we drag from a handle
+ local hitboxes = self:get_hitboxes()
+ local hit = self:hit_test(hitboxes, mouse_pos)
+
+ self.dragging = hit
+ self.anchor_pos = self:_get_anchor_positions()[10 - hit]
+
+ self.crop_ratio = (hitboxes[10][3] - hitboxes[10][1]) / (hitboxes[10][4] - hitboxes[10][2])
+ self.drag_start = mouse_pos
+
+ local handle_pos = self:_get_anchor_positions()[hit] or {mouse_pos.x, mouse_pos.y}
+ self.drag_offset = { mouse_pos.x - handle_pos[1], mouse_pos.y - handle_pos[2]}
+
+ self.restrict_ratio = shift_down
+
+ -- Start a new drag if not on handle
+ if self.dragging == 0 then
+ self.current_crop = { bound_mouse_pos, bound_mouse_pos }
+ self.crop_ratio = 1
+
+ self.dragging = 3
+ self.anchor_pos = {bound_mouse_pos.x, bound_mouse_pos.y}
+ -- self.drag_start = mouse_pos
+ end
+ end
+
+ else -- Mouse released
+
+ if xy_same(self.current_crop[1], self.current_crop[2]) and xy_distance(self.current_crop[1], mouse_pos) < 5 then
+ -- Mouse released after first click - ignore
+
+ elseif self.dragging > 0 then
+ -- Adjust current crop
+ self.current_crop = self:offset_crop_by_drag()
+ self.dragging = 0
+ end
+ end
+
+ end
+end
+
+
+function ASSCropper:_get_anchor_positions()
+ local x1, y1 = self.current_crop[1].x, self.current_crop[1].y
+ local x2, y2 = self.current_crop[2].x, self.current_crop[2].y
+ return {
+ [1] = {x1, y2},
+ [2] = {(x1+x2)/2, y2},
+ [3] = {x2, y2},
+
+ [4] = {x1, (y1+y2)/2},
+ [5] = {(x1+x2)/2, (y1+y2)/2},
+ [6] = {x2, (y1+y2)/2},
+
+ [7] = {x1, y1},
+ [8] = {(x1+x2)/2, y1},
+ [9] = {x2, y1},
+ }
+end
+
+
+function ASSCropper:offset_crop_by_drag()
+ -- Here be dragons lol
+ local vw, vh = self.display_state.video.width, self.display_state.video.height
+ local mx, my = self.mouse_video.x, self.mouse_video.y
+
+ local x1, x2 = self.current_crop[1].x, self.current_crop[2].x
+ local y1, y2 = self.current_crop[1].y, self.current_crop[2].y
+
+ local anchor_positions = self:_get_anchor_positions()
+
+ local handle = self.dragging
+ if handle > 0 then
+ local ax, ay = self.anchor_pos[1], self.anchor_pos[2]
+
+ local ox, oy = self.drag_offset[1], self.drag_offset[2]
+
+ local dx, dy = mx - ax - ox, my - ay - oy
+
+ -- Select active corner
+ if handle % 2 == 1 and handle ~= 5 then -- Change corners 4/6, 2/8
+ handle = (mx - ox < ax) and 1 or 3
+ handle = handle + ( (my - oy < ay) and 6 or 0)
+ else -- Change edges 1, 3, 7, 9
+ if handle == 4 and mx - ox > ax then
+ handle = 6
+ elseif handle == 6 and mx - ox < ax then
+ handle = 4
+ elseif handle == 2 and my - oy < ay then
+ handle = 8
+ elseif handle == 8 and my - oy > ay then
+ handle = 2
+ end
+ end
+
+ -- Handle booleans for logic
+ local h_bot = handle >= 1 and handle <= 3
+ local h_top = handle >= 7 and handle <= 9
+ local h_left = (handle - 1) % 3 == 0
+ local h_right = handle % 3 == 0
+
+ local h_horiz = handle == 4 or handle == 6
+ local h_vert = handle == 2 or handle == 8
+
+ -- Keep rect aspect ratio
+ if self.restrict_ratio then
+ local adx, ady = math.abs(dx), math.abs(dy)
+
+ -- Fit rect to mouse
+ local tmpy = adx / self.crop_ratio
+ if tmpy < ady then
+ adx = ady * self.crop_ratio
+ else
+ ady = tmpy
+ end
+
+ -- Figure out max size for corners, limit adx/ady
+ local max_w, max_h = vw, vh
+
+ if h_bot then
+ max_h = vh - ay -- Max height is from anchor to video bottom
+ elseif h_top then
+ max_h = ay -- Max height is from video bottom to anchor
+ elseif h_horiz then
+ -- Max height is closest edge * 2
+ max_h = math.min(vh - ay, ay) * 2
+ end
+
+ if h_left then
+ max_w = ax
+ elseif h_right then
+ max_w = vw - ax
+ elseif h_vert then
+ max_w = math.min(vw - ax, ax) * 2
+ end
+
+ -- Limit size to corners
+ if handle ~= 5 then
+ -- TODO this can be done tidier?
+
+ -- If wider than max width, scale down
+ if adx > max_w then
+ adx = max_w
+ ady = adx / self.crop_ratio
+ end
+ -- If taller than max height, scale down
+ if ady > max_h then
+ ady = max_h
+ adx = ady * self.crop_ratio
+ end
+ end
+
+ -- Hacky offsets
+ if handle == 1 then
+ dx = -adx
+ dy = ady
+ elseif handle == 2 then
+ dx = adx
+ dy = ady
+ elseif handle == 3 then
+ dx = adx
+ dy = ady
+
+ elseif handle == 4 then
+ dx = -adx
+ dy = ady
+ elseif handle == 5 then
+ -- pass
+ elseif handle == 6 then
+ dx = adx
+ dy = ady
+
+ elseif handle == 7 then
+ dy = -ady
+ dx = -adx
+ elseif handle == 8 then
+ dx = adx
+ dy = -ady
+ elseif handle == 9 then
+ dx = adx
+ dy = -ady
+ end
+ end
+
+ -- Can this be done not-manually?
+ -- Re-create the rect with some corners anchored etc
+ if handle == 5 then
+ -- Simply move the box around
+ x1, x2 = x1+dx, x2+dx
+ y1, y2 = y1+dy, y2+dy
+
+ elseif handle == 1 then
+ x1, x2 = ax + dx, ax
+ y1, y2 = ay, ay+dy
+
+ elseif handle == 2 then
+ y1, y2 = ay, ay + dy
+
+ if self.restrict_ratio then
+ x1, x2 = ax - dx/2, ax + dx/2
+ end
+
+ elseif handle == 3 then
+ x1, x2 = ax, ax + dx
+ y1, y2 = ay, ay + dy
+
+ elseif handle == 4 then
+ x1, x2 = ax + dx, ax
+
+ if self.restrict_ratio then
+ y1, y2 = ay - dy/2, ay + dy/2
+ end
+
+ elseif handle == 6 then
+ x1, x2 = ax, ax + dx
+
+ if self.restrict_ratio then
+ y1, y2 = ay - dy/2, ay + dy/2
+ end
+
+
+ elseif handle == 7 then
+ x1, x2 = ax + dx, ax
+ y1, y2 = ay + dy, ay
+
+ elseif handle == 8 then
+ y1, y2 = ay + dy, ay
+
+ if self.restrict_ratio then
+ x1, x2 = ax - dx/2, ax + dx/2
+ end
+
+ elseif handle == 9 then
+ x1, x2 = ax, ax + dx
+ y1, y2 = ay + dy, ay
+ end
+
+
+ if self.dragging == 5 then
+ -- On moving the entire box, we have to figure out how much to "offset" every corner if we go over the edge
+ local x_min = math.max(0, 0-x1)
+ local y_min = math.max(0, 0-y1)
+
+ local x_max = math.max(0, x2-vw)
+ local y_max = math.max(0, y2-vh)
+
+ x1 = x1 + x_min - x_max
+ y1 = y1 + y_min - y_max
+ x2 = x2 + x_min - x_max
+ y2 = y2 + y_min - y_max
+ elseif not self.restrict_ratio then
+ -- This is already done for restricted ratios, hence the if
+
+ -- Constrict the crop to video space
+ -- Since one corner/edge is moved at a time, we can just minmax this
+ x1, x2 = math.max(0, x1), math.min(vw, x2)
+ y1, y2 = math.max(0, y1), math.min(vh, y2)
+ end
+ end -- /drag
+
+ if self.dragging > 0 and self.options.even_dimensions then
+ local w, h = x2 - x1, y2 - y1
+ local even_w = w - (w % 2)
+ local even_h = h - (h % 2)
+
+ if handle == 1 or handle == 2 or handle == 3 then
+ y2 = y1 + even_h
+ elseif handle == 7 or handle == 8 or handle == 9 then
+ y1 = y2 - even_h
+ end
+ if handle == 1 or handle == 4 or handle == 7 then
+ x1 = x2 - even_w
+ elseif handle == 3 or handle == 6 or handle == 9 then
+ x2 = x1 + even_w
+ end
+ end
+
+ local fx1, fx2 = order_pair(math.floor(x1), math.floor(x2))
+ local fy1, fy2 = order_pair(math.floor(y1), math.floor(y2))
+
+ -- msg.info(fx1, fy1, fx2, fy2, handle)
+
+ return { {x=fx1, y=fy1}, {x=fx2, y=fy2} }, handle
+end
+
+
+function order_pair( a, b )
+ if a < b then
+ return a, b
+ else
+ return b, a
+ end
+end
+
+
+function ASSCropper:render()
+ -- For debugging
+ local ass_txt = self:get_render_ass()
+
+ local ds = self.display_state
+ mp.set_osd_ass(ds.screen.width, ds.screen.height, ass_txt)
+end
+
+
+function ASSCropper:get_render_ass(dim_only)
+ if not self.display_state.video_ready then
+ msg.info("No video info on display_state")
+ return ""
+ end
+
+ line_color = self.options.color_invert and 20 or 220
+ local guide_format = string.format("{\\3a&HFF&\\3a&H%02X&\\3c&H%02X%02X%02X&\\bord1\\shad0}", 128, line_color, line_color, line_color)
+
+ ass = assdraw.ass_new()
+ if self.current_crop then
+
+ if self.testing_crop then
+ -- Just draw simple help
+ ass:new_event()
+ ass:pos(self.display_state.screen.width - 5, 5)
+ ass:append( string.format("{\\fs%d\\an%d\\bord2}", self.text_size, 9) )
+
+ local fmt_key = function( key, text ) return string.format("[{\\c&HBEBEBE&}%s{\\c} %s]", key:upper(), text) end
+
+ ass:append(fmt_key("ENTER", "Accept crop") .. " " .. fmt_key("ESC", "Cancel crop") .. '\\N' .. fmt_key("T", "Stop testing"))
+ return ass.text
+ end
+
+ local temp_crop, drawn_handle = self:offset_crop_by_drag()
+ local v_hb = self:get_hitboxes(temp_crop)
+ -- Map coords to screen
+ local s_hb = {}
+ for index, coords in pairs(v_hb) do
+ local x1, y1 = self.display_state:video_to_screen(coords[1], coords[2])
+ local x2, y2 = self.display_state:video_to_screen(coords[3], coords[4])
+ s_hb[index] = {x1, y1, x2, y2}
+ end
+
+ -- Full crop
+ local v_crop = v_hb[10] -- Video-space
+ local s_crop = s_hb[10] -- Screen-space
+
+
+ -- Inverse clipping for the crop box
+ ass:new_event()
+ ass:append(string.format("{\\iclip(%d,%d,%d,%d)}", s_crop[1], s_crop[2], s_crop[3], s_crop[4]))
+
+ -- Dim overlay
+ local format_dim = string.format("{\\bord0\\1a&H%02X&\\1c&H%02X%02X%02X&}", self.overlay_transparency, self.overlay_lightness, self.overlay_lightness, self.overlay_lightness)
+ ass:pos(0,0)
+ ass:draw_start()
+ ass:append(format_dim)
+ ass:rect_cw(0, 0, self.display_state.screen.width, self.display_state.screen.height)
+ ass:draw_stop()
+
+ if dim_only then -- Early out with just the dim outline
+ return ass.text
+ end
+
+ if draw_text then
+ -- Text on end
+ ass:new_event()
+ ass:pos(ce_x, ce_y)
+ -- Text align
+ local txt_a = ((ce_x > cs_x) and 3 or 1) + ((ce_y > cs_y) and 0 or 6)
+ ass:an( txt_a )
+ ass:append("{\\fs20\\shad0\\be0\\bord2}")
+ ass:append(string.format("%dx%d", math.abs(ce_x-cs_x), math.abs(ce_y-cs_y)) )
+ end
+
+
+ local box_format = string.format("{\\1a&HFF&\\3a&H%02X&\\3c&H%02X%02X%02X&\\bord1}", 0, line_color, line_color, line_color)
+ local handle_hilight_format = string.format("{\\1a&H%02X&\\3a&H%02X&\\3c&H%02X%02X%02X&\\bord0}", 230, 0, line_color, line_color, line_color)
+ local handle_drag_format = string.format("{\\1a&H%02X&\\3a&H%02X&\\3c&H%02X%02X%02X&\\bord1}", 200, 0, line_color, line_color, line_color)
+
+ -- Main crop box
+ ass:new_event()
+ ass:pos(0,0)
+ ass:append( box_format )
+ ass:draw_start()
+ ass:rect_cw(s_crop[1], s_crop[2], s_crop[3], s_crop[4])
+ ass:draw_stop()
+
+ -- Guide grid, 3x3
+ if self.options.guide_type then
+ ass:new_event()
+ ass:pos(0,0)
+ ass:append( guide_format )
+ ass:draw_start()
+
+ local w = (s_crop[3] - s_crop[1])
+ local h = (s_crop[4] - s_crop[2])
+
+ local w_3rd = w / 3
+ local h_3rd = h / 3
+ local w_2 = w / 2
+ local h_2 = h / 2
+ if self.options.guide_type == 1 then
+ -- 3x3 grid
+ ass:move_to(s_crop[1] + w_3rd, s_crop[2])
+ ass:line_to(s_crop[1] + w_3rd, s_crop[4])
+
+ ass:move_to(s_crop[1] + w_3rd*2, s_crop[2])
+ ass:line_to(s_crop[1] + w_3rd*2, s_crop[4])
+
+ ass:move_to(s_crop[1], s_crop[2] + h_3rd)
+ ass:line_to(s_crop[3], s_crop[2] + h_3rd)
+
+ ass:move_to(s_crop[1], s_crop[2] + h_3rd*2)
+ ass:line_to(s_crop[3], s_crop[2] + h_3rd*2)
+
+ elseif self.options.guide_type == 2 then
+ -- Top to bottom
+ ass:move_to(s_crop[1] + w_2, s_crop[2])
+ ass:line_to(s_crop[1] + w_2, s_crop[4])
+
+ -- Left to right
+ ass:move_to(s_crop[1], s_crop[2] + h_2)
+ ass:line_to(s_crop[3], s_crop[2] + h_2)
+ end
+ ass:draw_stop()
+ end
+
+ if self.dragging > 0 and drawn_handle ~= 5 then
+ -- While dragging, draw only the dragging handle
+ ass:new_event()
+ ass:append( handle_drag_format )
+ ass:pos(0,0)
+ ass:draw_start()
+ ass:rect_cw(s_hb[drawn_handle][1], s_hb[drawn_handle][2], s_hb[drawn_handle][3], s_hb[drawn_handle][4])
+ ass:draw_stop()
+ elseif self.dragging == 0 then
+ local hit_index = self:hit_test(s_hb, self.mouse_screen)
+ if hit_index > 0 and hit_index ~= 5 then
+ -- Hilight handle
+ ass:new_event()
+ ass:append( handle_hilight_format )
+ ass:pos(0,0)
+ ass:draw_start()
+ ass:rect_cw(s_hb[hit_index][1], s_hb[hit_index][2], s_hb[hit_index][3], s_hb[hit_index][4])
+ ass:draw_stop()
+ end
+
+ ass:new_event()
+ ass:pos(0,0)
+ ass:append( box_format )
+ ass:draw_start()
+
+ -- Draw corner handles
+ for k, v in pairs({1, 3, 7, 9}) do
+ ass:rect_cw(s_hb[v][1], s_hb[v][2], s_hb[v][3], s_hb[v][4])
+ end
+ ass:draw_stop()
+ end
+
+ if true or draw_text then
+
+ local br_pos = {s_crop[3] - 2, s_crop[4] + 2}
+ local br_align = 9
+ if br_pos[2] >= self.display_state.screen.height - 20 then
+ br_pos[2] = br_pos[2] - 4
+ br_align = 3
+ end
+
+ ass:new_event()
+ ass:pos(unpack(br_pos))
+ ass:an( br_align )
+ ass:append("{\\fs20\\shad0\\be0\\bord2}")
+ ass:append(string.format("%dx%d", v_crop[3] - v_crop[1], v_crop[4] - v_crop[2]) )
+
+ local tl_pos = {s_crop[1] + 2, s_crop[2] - 2}
+ local tl_align = 1
+ if tl_pos[2] < 20 then
+ tl_pos[2] = tl_pos[2] + 4
+ tl_align = 7
+ end
+
+ ass:new_event()
+ ass:pos(unpack(tl_pos))
+ ass:an( tl_align )
+ ass:append("{\\fs20\\shad0\\be0\\bord2}")
+ ass:append(string.format("%d,%d", v_crop[1], v_crop[2]))
+ end
+
+ ass:draw_stop()
+ end
+
+ -- Crosshair for mouse
+ if self.options.draw_mouse and not dim_only then
+ ass:new_event()
+ ass:pos(0,0)
+ ass:append( guide_format )
+ ass:draw_start()
+
+ ass:move_to(self.mouse_screen.x, 0)
+ ass:line_to(self.mouse_screen.x, self.display_state.screen.height)
+
+ ass:move_to(0, self.mouse_screen.y)
+ ass:line_to(self.display_state.screen.width, self.mouse_screen.y)
+
+ ass:draw_stop()
+ end
+
+ if self.options.draw_help and not dim_only then
+ ass:new_event()
+ ass:pos(self.display_state.screen.width - 5, 5)
+ local text_align = 9
+ ass:append( string.format("{\\fs%d\\an%d\\bord2}", self.text_size, text_align) )
+
+ local fmt_key = function( key, text ) return string.format("[{\\c&HBEBEBE&}%s{\\c} %s]", key:upper(), text) end
+
+ local crosshair_txt = self.options.draw_mouse and "Hide" or "Show";
+ lines = {
+ fmt_key("ENTER", "Accept crop") .. " " .. fmt_key("ESC", "Cancel crop") .. " " .. fmt_key("D", "Autodetect crop") .. " " .. fmt_key("T", "Test crop"),
+ fmt_key("SHIFT-Drag", "Constrain ratio") .. " " .. fmt_key("SHIFT-Arrow", "Nudge"),
+ fmt_key("C", crosshair_txt .. " crosshair") .. " " .. fmt_key("X", "Cycle guides") .. " " .. fmt_key("Z", "Invert color"),
+ }
+
+ local full_line = nil
+ for i, line in pairs(lines) do
+ if line ~= nil then
+ full_line = full_line and (full_line .. "\\N" .. line) or line
+ end
+ end
+ ass:append(full_line)
+ end
+
+ return ass.text
+end
+--[[
+ A tool to expand properties in template strings, mimicking mpv's
+ property expansion but with a few extras (like formatting times).
+
+ Depends on helpers.lua (isempty)
+]]--
+
+local PropertyExpander = {}
+PropertyExpander.__index = PropertyExpander
+
+setmetatable(PropertyExpander, {
+ __call = function (cls, ...) return cls.new(...) end
+})
+
+function PropertyExpander.new(property_source)
+ local self = setmetatable({}, PropertyExpander)
+ self.sentinel = {}
+
+ -- property_source is a table which defines the following functions:
+ -- get_raw_property(name, def) - returns a raw property or def
+ -- get_property(name) - returns a string
+ -- get_property_osd(name) - returns an OSD formatted string (whatever that'll mean)
+ self.property_source = property_source
+ return self
+end
+
+
+-- Formats seconds to H:M:S based on a %h-%m-%s format
+function PropertyExpander:_format_time(seconds, time_format)
+ -- In case "seconds" is not a number, give it back
+ if type(seconds) ~= "number" then
+ return seconds
+ end
+
+ time_format = time_format or "%02h.%02m.%06.3s"
+
+ local types = { h='d', m='d', s='f', S='f', M='d' }
+ local values = {
+ h=math.floor(seconds / 3600),
+ m=math.floor((seconds % 3600) / 60),
+ s=(seconds % 60),
+ S=seconds,
+ M=math.floor((seconds % 1)*1000)
+ }
+
+ local substitutor = function(sub_format, char)
+ local v = values[char]
+ local t = types[char]
+ if t == nil then return nil end
+
+ sub_format = '%' .. sub_format .. types[char]
+ return v and sub_format:format(v) or nil
+ end
+
+ return time_format:gsub('%%([%-%+ #0]*%d*.?%d*)([%a%%])', substitutor)
+end
+
+-- Format a date
+function PropertyExpander:_format_date(seconds, date_format)
+ -- In case "seconds" is not nil or a number, give it back
+ if type(seconds) ~= "number" and type(seconds) ~= "nil" then
+ return seconds
+ end
+
+ --[[
+ As stated by Lua docs:
+ %a abbreviated weekday name (e.g., Wed)
+ %A full weekday name (e.g., Wednesday)
+ %b abbreviated month name (e.g., Sep)
+ %B full month name (e.g., September)
+ %c date and time (e.g., 09/16/98 23:48:10)
+ %d day of the month (16) [01-31]
+ %H hour, using a 24-hour clock (23) [00-23]
+ %I hour, using a 12-hour clock (11) [01-12]
+ %M minute (48) [00-59]
+ %m month (09) [01-12]
+ %p either "am" or "pm" (pm)
+ %S second (10) [00-61]
+ %w weekday (3) [0-6 = Sunday-Saturday]
+ %x date (e.g., 09/16/98)
+ %X time (e.g., 23:48:10)
+ %Y full year (1998)
+ %y two-digit year (98) [00-99]
+ %% the character `%´
+ ]]--
+ date_format = date_format or "%Y-%m-%d %H-%M-%S"
+ return os.date(date_format, seconds)
+end
+
+
+function PropertyExpander:expand(format_string)
+ local comparisons = {
+ {
+ -- Less than or equal
+ '^(..-)<=(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "number" then return nil end
+ return property_value <= tonumber(other_value)
+ end
+ },
+ {
+ -- More than or equal
+ '^(..-)>=(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "number" then return nil end
+ return property_value >= tonumber(other_value)
+ end
+ },
+ {
+ -- Less than
+ '^(..-)<(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "number" then return nil end
+ return property_value < tonumber(other_value)
+ end
+ },
+ {
+ -- More than
+ '^(..-)>(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "number" then return nil end
+ return property_value > tonumber(other_value)
+ end
+ },
+ {
+ -- Equal
+ '^(..-)==(.+)$',
+ function(property_value, other_value)
+ if type(property_value) == "number" then
+ other_value = tonumber(other_value)
+ elseif type(property_value) ~= "string" then
+ -- Ignore booleans and others
+ return nil
+ end
+ return property_value == other_value
+ end
+ },
+ {
+ -- Starts with
+ '^(..-)^=(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "string" then return nil end
+ return property_value:sub(1, other_value:len()) == other_value
+ end
+ },
+ {
+ -- Ends with
+ '^(..-)$=(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "string" then return nil end
+ return other_value == '' or property_value:sub(-other_value:len()) == other_value
+ end
+ },
+ {
+ -- Contains
+ '^(..-)~=(.+)$',
+ function(property_value, other_value)
+ if type(property_value) ~= "string" then return nil end
+ return property_value:find(other_value, nil, true) ~= nil
+ end
+ },
+ }
+
+ local substitutor = function(match)
+ local command, inner = match:sub(3, -2):match('^([%?!~^%%#&]?)(.+)$')
+ local colon_index = inner:find(':')
+
+ local property_name = inner
+ local secondary = ""
+ local has_colon = colon_index and true or false
+
+ if colon_index then
+ property_name = inner:sub(1, colon_index-1)
+ secondary = inner:sub(colon_index+1, -1)
+ end
+
+ local used_comparison = nil
+ local comparison_value = nil
+ for i, comparison in ipairs(comparisons) do
+ local name, other_value = property_name:match(comparison[1])
+ if name then
+ property_name = name
+ comparison_value = other_value
+ used_comparison = comparison[2]
+ break
+ end
+ end
+
+ local raw_property_value = self.property_source:get_raw_property(property_name, self.sentinel)
+ local property_exists = raw_property_value ~= self.sentinel
+
+ if command == '' then
+ if used_comparison then
+ if used_comparison(raw_property_value, comparison_value) then return self:expand(secondary)
+ else return '' end
+ end
+
+ -- Return the property value if it's not nil, else the (expanded) secondary
+ return property_exists and self.property_source:get_property(property_name) or self:expand(secondary)
+
+
+ elseif command == '?' then
+ -- Return (expanded) secondary if property is truthy (sentinel is falsey)
+ if not isempty(raw_property_value) then return self:expand(secondary) else return '' end
+
+ elseif command == '!' then
+ if used_comparison then
+ if not used_comparison(raw_property_value, comparison_value) then return self:expand(secondary)
+ else return '' end
+ end
+
+ -- Return (expanded) secondary if property is falsey
+ if isempty(raw_property_value) then return self:expand(secondary) else return '' end
+
+
+ elseif command == '^' then
+ -- Return (expanded) secondary if property does not exist
+ return not property_exists and self:expand(secondary) or ""
+
+
+ elseif command == '%' then
+ -- Return the value formatted using the secondary string
+ return secondary:format(raw_property_value)
+
+ elseif command == '#' then
+ -- Format a number to HMS
+ return self:_format_time(raw_property_value, has_colon and secondary or nil)
+
+ elseif command == '&' then
+ -- Format a date
+ return self:_format_date(nil, has_colon and secondary or nil)
+
+
+ elseif command == '@' then
+ -- Format the value for OSD - mostly useful for latching onto mpv's properties
+ return property_exists and self.property_source:get_property_osd(property_name) or self:expand(secondary)
+ end
+
+ end
+
+ -- Lua patterns are generally a pain, but %b is comfy!
+ local expanded = format_string:gsub('%$%b{}', substitutor)
+ return expanded
+end
+
+
+local MPVPropertySource = {}
+MPVPropertySource.__index = MPVPropertySource
+
+setmetatable(MPVPropertySource, {
+ __call = function (cls, ...) return cls.new(...) end
+})
+
+function MPVPropertySource.new(values)
+ local self = setmetatable({}, MPVPropertySource)
+ self.values = values
+
+ return self
+end
+
+function MPVPropertySource:get_raw_property(name, default)
+ if name:find('mpv/') ~= nil then
+ return mp.get_property_native(name:sub(5), default)
+ end
+ local v = self.values[name]
+ if v ~= nil then return v else return default end
+end
+
+function MPVPropertySource:get_property(name, default)
+ if name:find('mpv/') ~= nil then
+ return mp.get_property(name:sub(5), default)
+ end
+ local v = self.values[name]
+ if v ~= nil then return tostring(v) else return default end
+end
+
+function MPVPropertySource:get_property_osd(name, default)
+ if name:find('mpv/') ~= nil then
+ return mp.get_property_osd(name:sub(5), default)
+ end
+ local v = self.values[name]
+ if v ~= nil then return tostring(v) else return default end
+end
+function script_crop_toggle()
+ if asscropper.active then
+ asscropper:stop_crop(true)
+ else
+ local on_crop = function(crop)
+ mp.set_osd_ass(0, 0, "")
+ screenshot(crop)
+ end
+ local on_cancel = function()
+ mp.osd_message("Crop canceled")
+ mp.set_osd_ass(0, 0, "")
+ end
+
+ local crop_options = {
+ guide_type = ({none=0, grid=1, center=2})[option_values.guide_type],
+ draw_mouse = option_values.draw_mouse,
+ color_invert = option_values.color_invert,
+ auto_invert = option_values.auto_invert
+ }
+ asscropper:start_crop(crop_options, on_crop, on_cancel)
+ if not asscropper.active then
+ mp.osd_message("No video to crop!", 2)
+ end
+ end
+end
+
+
+local next_tick_time = nil
+function on_tick_listener()
+ local now = mp.get_time()
+ if next_tick_time == nil or now >= next_tick_time then
+ if asscropper.active and display_state:recalculate_bounds() then
+ mp.set_osd_ass(display_state.screen.width, display_state.screen.height, asscropper:get_render_ass())
+ end
+ next_tick_time = now + (1/60)
+ end
+end
+
+
+function expand_output_path(cropbox)
+ local filename = mp.get_property_native("filename")
+ local playback_time = mp.get_property_native("playback-time")
+ local duration = mp.get_property_native("duration")
+
+ local filename_without_ext, extension = filename:match("^(.+)%.(.-)$")
+
+ local properties = {
+ path = mp.get_property_native("path"), -- Original path
+
+ filename = filename_without_ext or filename, -- Filename without extension (or filename if no dots
+ file_ext = extension or "", -- Original extension without leading dot (or empty string)
+
+ pos = mp.get_property_native("playback-time"),
+
+ full = false,
+ is_image = (duration == 0 and playback_time == 0),
+
+ crop_w = cropbox.w,
+ crop_h = cropbox.h,
+ crop_x = cropbox.x,
+ crop_y = cropbox.y,
+ crop_x2 = cropbox.x2,
+ crop_y2 = cropbox.y2,
+
+ unique = 0,
+
+ ext = option_values.output_extension
+ }
+ local propex = PropertyExpander(MPVPropertySource(properties))
+
+
+ local test_path = propex:expand(option_values.output_template)
+ -- If the paths do not change when incrementing the unique, it's not used.
+ -- Return early and avoid the endless loop
+ properties.unique = 1
+ if propex:expand(option_values.output_template) == test_path then
+ properties.full = true
+ local temporary_screenshot_path = propex:expand(option_values.output_template)
+ return test_path, temporary_screenshot_path
+
+ else
+ -- Figure out an unique filename
+ while true do
+ test_path = propex:expand(option_values.output_template)
+
+ -- Check if filename is free
+ if not path_exists(test_path) then
+ properties.full = true
+ local temporary_screenshot_path = propex:expand(option_values.output_template)
+ return test_path, temporary_screenshot_path
+ else
+ -- Try the next one
+ properties.unique = properties.unique + 1
+ end
+ end
+ end
+end
+
+
+function screenshot(crop)
+ local size = round_dec(crop.w) .. "x" .. round_dec(crop.h)
+
+ -- Bail on bad crop sizes
+ if not (crop.w > 0 and crop.h > 0) then
+ mp.osd_message("Bad crop (" .. size .. ")!")
+ return
+ end
+
+ local output_path, temporary_screenshot_path = expand_output_path(crop)
+
+ -- Optionally create directories
+ if option_values.create_directories then
+ local paths = {}
+ paths[1] = path_utils.dirname(output_path)
+ paths[2] = path_utils.dirname(temporary_screenshot_path)
+
+ -- Check if we can read the paths
+ for i, path in ipairs(paths) do
+ local l, err = utils.readdir(path)
+ if err then
+ create_directories(path)
+ end
+ end
+ end
+
+ local playback_time = mp.get_property_native("playback-time")
+ local duration = mp.get_property_native("duration")
+
+ local input_path = nil
+
+ if option_values.skip_screenshot_for_images and duration == 0 and playback_time == 0 then
+ -- Seems to be an image (or at least static file)
+ input_path = mp.get_property_native("path")
+ temporary_screenshot_path = nil
+ else
+ -- Not an image, take a temporary screenshot
+
+ -- In case the full-size output path is identical to the crop path,
+ -- crudely make it different
+ if temporary_screenshot_path == output_path then
+ temporary_screenshot_path = temporary_screenshot_path .. "_full.png"
+ end
+
+ -- Temporarily lower the PNG compression
+ local previous_png_compression = mp.get_property_native("screenshot-png-compression")
+ mp.set_property_native("screenshot-png-compression", 0)
+ -- Take the screenshot
+ mp.commandv("raw", "no-osd", "screenshot-to-file", temporary_screenshot_path)
+ -- Return the previous value
+ mp.set_property_native("screenshot-png-compression", previous_png_compression)
+
+ if not path_exists(temporary_screenshot_path) then
+ msg.error("Failed to take screenshot: " .. temporary_screenshot_path)
+ mp.osd_message("Unable to save screenshot")
+ return
+ end
+
+ input_path = temporary_screenshot_path
+ end
+
+ local crop_string = string.format("%d:%d:%d:%d", crop.w, crop.h, crop.x, crop.y)
+ local cmd = {
+ args = {
+ "mpv", input_path,
+ "--no-config",
+ "--vf=crop=" .. crop_string,
+ "--frames=1",
+ "--ovc=" .. option_values.output_format,
+ "-o", output_path
+ }
+ }
+
+ msg.info("Cropping: ", crop_string, output_path)
+ local ret = utils.subprocess(cmd)
+
+ if not option_values.keep_original and temporary_screenshot_path then
+ os.remove(temporary_screenshot_path)
+ end
+
+ if ret.error or ret.status ~= 0 then
+ mp.osd_message("Screenshot failed, see console for details")
+ msg.error("Crop failed! mpv exit code: " .. tostring(ret.status))
+ msg.error("mpv stdout:")
+ msg.error(ret.stdout)
+ else
+ msg.info("Crop finished!")
+ mp.osd_message("Took screenshot (" .. size .. ")")
+ end
+end
+
+----------------------
+-- Instances, binds --
+----------------------
+
+-- Sanity-check output_template
+if option_values.warn_about_template and not option_values.output_template:find('%${ext}') then
+ msg.warn("Output template missing ${ext}! If this is desired, set warn_about_template=yes in config!")
+end
+
+-- Short list of extensions for encoders
+local ENCODER_EXTENSION_MAP = {
+ png = "png",
+ mjpeg = "jpg",
+ targa = "tga",
+ tiff = "tiff",
+ gif = "gif", -- please don't
+ bmp = "bmp",
+ jpegls = "jpg",
+ ljpeg = "jpg",
+ jpeg2000 = "jp2",
+}
+-- Pick an extension if one was not provided
+if option_values.output_extension == "" then
+ local extension = ENCODER_EXTENSION_MAP[option_values.output_format]
+ if not extension then
+ msg.error("Unrecognized output format '" .. option_values.output_format .. "', unable to pick an extension! Bailing!")
+ mp.osd_message("mpv_crop_script was unable to choose an extension, check your config", 3)
+ end
+ option_values.output_extension = extension
+end
+
+
+display_state = DisplayState()
+asscropper = ASSCropper(display_state)
+asscropper.overlay_transparency = option_values.overlay_transparency
+asscropper.overlay_lightness = option_values.overlay_lightness
+
+asscropper.tick_callback = on_tick_listener
+mp.register_event("tick", on_tick_listener)
+
+local used_keybind = SCRIPT_KEYBIND
+-- Disable the default keybind if asked to
+if option_values.disable_keybind then
+ used_keybind = nil
+end
+mp.add_key_binding(used_keybind, SCRIPT_HANDLER, script_crop_toggle)
diff --git a/ar/.config/mpv/scripts/navigator.lua b/ar/.config/mpv/scripts/navigator.lua
new file mode 100644
index 0000000..af7595c
--- /dev/null
+++ b/ar/.config/mpv/scripts/navigator.lua
@@ -0,0 +1,603 @@
+local utils = require("mp.utils")
+local mpopts = require("mp.options")
+local assdraw = require("mp.assdraw")
+
+ON_WINDOWS = (package.config:sub(1, 1) ~= "/")
+WINDOWS_ROOTDIR = false
+WINDOWS_ROOT_DESC = "Select drive"
+SEPARATOR_WINDOWS = "\\"
+
+SEPARATOR = "/"
+
+local windows_desktop = ON_WINDOWS
+ and utils.join_path(os.getenv("USERPROFILE"), "Desktop"):gsub(SEPARATOR, SEPARATOR_WINDOWS) .. SEPARATOR_WINDOWS
+ or nil
+
+local settings = {
+ --navigation keybinds override arrowkeys and enter when activating navigation menu, false means keys are always actíve
+ dynamic_binds = true,
+ navigator_mainkey = "shift+v", --the key to bring up navigator's menu, can be bound on input.conf aswell
+
+ --dynamic binds, should not be bound in input.conf unless dynamic binds is false
+ key_navfavorites = "ctrl+f",
+ key_navup = "k",
+ key_navdown = "j",
+ key_navback = "h",
+ key_navforward = "l",
+ key_navopen = "ENTER",
+ key_navclose = "q",
+
+ --fallback if no file is open, should be a string that points to a path in your system
+ defaultpath = windows_desktop or os.getenv("HOME") or "/",
+ forcedefault = false, --force navigation to start from defaultpath instead of currently playing file
+ --favorites in format { 'Path to directory, notice trailing /' }
+ --on windows use double backslash c:\\my\\directory\\
+ favorites = {
+ "/media/si",
+ "/mnt/second/videos",
+ "/home/si/Downloads",
+ "/home/si/Torrents/complete",
+ "/home/si/Videos",
+ "/home/si/.config/mpv/playlists",
+ },
+ --list of paths to ignore. the value is anything that returns true for if-statement.
+ --directory ignore entries must end with a trailing slash,
+ --but files and all symlinks (even to dirs) must be without slash!
+ --to help you with the format, simply run "ls -1p <parent folder>" in a terminal,
+ --and you will see if the file/folder to ignore is listed as "file" or "folder/" (trailing slash).
+ --you can ignore children without ignoring their parent.
+ ignorePaths = {
+ --general linux system paths (some are used by macOS too):
+ ["/bin/"] = "1",
+ ["/boot/"] = "1",
+ ["/cdrom/"] = "1",
+ ["/dev/"] = "1",
+ ["/etc/"] = "1",
+ ["/lib/"] = "1",
+ ["/lib32/"] = "1",
+ ["/lib64/"] = "1",
+ ["/tmp/"] = "1",
+ ["/srv/"] = "1",
+ ["/sys/"] = "1",
+ ["/snap/"] = "1",
+ ["/root/"] = "1",
+ ["/sbin/"] = "1",
+ ["/proc/"] = "1",
+ ["/opt/"] = "1",
+ ["/usr/"] = "1",
+ ["/run/"] = "1",
+ --useless macOS system paths (some of these standard folders are actually files (symlinks) into /private/ subpaths, hence some repetition):
+ ["/cores/"] = "1",
+ ["/etc"] = "1",
+ ["/installer.failurerequests"] = "1",
+ ["/net/"] = "1",
+ ["/private/"] = "1",
+ ["/tmp"] = "1",
+ ["/var"] = "1",
+ },
+ --ignore folders and files that match patterns regardless of where they exist on disk.
+ --make sure you use ^ (start of string) and $ (end of string) to catch the whole str instead of risking partial false positives.
+ --read about patterns at https://www.lua.org/pil/20.2.html or http://lua-users.org/wiki/PatternsTutorial
+ ignorePatterns = {
+ "^initrd%..*/?$", --hide files and folders folders starting with "initrd.<something>"
+ "^vmlinuz.*/?$", --hide files and folders starting with "vmlinuz<something>"
+ "^lost%+found/?$", --hide files and folders named "lost+found"
+ "^%$.*$", --ignore files starting with $
+ "^.*%.ico$",
+ "^.*%.txt$",
+ "^.*%.ahk$",
+ "^.*%.reg$",
+ "^.*%.exe$",
+ "^.*%.bin$",
+ "^.*%.mpq$",
+ "^.*%.inf$",
+ "^.*%.pdf$",
+ "^.*%.docx$",
+ "^.*%.xlsx$",
+ "^.*%.pptx$",
+ "^.*%.zip$",
+ "^.*%.rar$",
+ "^.*%.tar$",
+ "^.*%.gz$",
+ "^.*%.bz2$",
+ "^.*%.7z$",
+ "^.*%.pkg$",
+ "^.*%.deb$",
+ "^.*%.rpm$",
+ "^.*%.dll$",
+ "^.*%.sys$",
+ "^.*%.cfg$",
+ "^.*%.ini$",
+ "^.*%.dat$",
+ "^.*%.bat$",
+ "^.*%.cmd$",
+ "^.*%.js$",
+ "^.*%.html$",
+ "^.*%.htm$",
+ "^.*%.css$",
+ "^.*%.xml$",
+ "^.*%.json$",
+ "^.*%.csv$",
+ "^.*%.md$",
+ "^.*%.yml$",
+ "^.*%.yaml$",
+ "^.*%.sql$",
+ "^.*%.py$",
+ "^.*%.java$",
+ "^.*%.c$",
+ "^.*%.cpp$",
+ "^.*%.h$",
+ "^.*%.rb$",
+ "^.*%.pl$",
+ "^.*%.php$",
+ "^.*%.asp$",
+ "^.*%.aspx$",
+ "^.*%.jsp$",
+ "^.*%.sh$",
+ "^.*%.vbs$",
+ "^.*%.ps1$",
+ "^.*%.log$",
+ "^.*%.msg$",
+ "^.*%.eml$",
+ "^.*%.ics$",
+ "^.*%.vcard$",
+ "^.*%.vcf$",
+ "^.*%.mdf$",
+ "^.*%.ldf$",
+ "^.*%.nfo$",
+ "^.*%.tmp$",
+ "^.*%.bak$",
+ "^.*%.hax$",
+ },
+
+ subtitleformats = {
+ "srt",
+ "smi",
+ "ass",
+ "lrc",
+ "ssa",
+ "ttml",
+ "sbv",
+ "vtt",
+ "txt",
+ },
+
+ navigator_menu_favkey = "F", --this key will always be bound when the menu is open, and is the key you use to cycle your favorites list!
+ menu_timeout = false, --menu timeouts and closes itself after navigator_duration seconds, else will be toggled by keybind
+ navigator_duration = 13, --osd duration before the navigator closes, if timeout is set to true
+ visible_item_count = 20, --how many menu items to show per screen
+
+ --font size scales by window, if false requires larger font and padding sizes
+ scale_by_window = true,
+ --paddings from top left corner
+ text_padding_x = 10,
+ text_padding_y = 20,
+ -- --ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
+ -- --example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
+ -- --read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
+ -- --undeclared tags will use default osd settings
+ -- --these styles will be used for the whole navigator
+ -- style_ass_tags = "{}",
+ -- --you can also use the ass tags mentioned above. For example:
+ -- --selection_prefix="{\\c&HFF00FF&}● " - to add a color for selected file. However, if you
+ -- --use ass tags you need to set them for both name and selection prefix (see https://github.com/jonniek/mpv-playlistmanager/issues/20)
+ -- name_prefix = "○ ",
+ -- selection_prefix = "● ",
+
+ -- For white color:
+ style_ass_tags = "{\\q2\\an7\\fnUbuntu\\fs8\\b0\\bord1\\c&HFFFFFF&}",
+ name_prefix = "{\\c&HFFFFFF&}○ ",
+
+ -- For orange selection:
+ selection_prefix = "{\\c&H0080FF&}● ",
+}
+
+mpopts.read_options(settings)
+
+--escape a file or directory path for use in shell arguments
+function escapepath(dir, escapechar)
+ return string.gsub(dir, escapechar, "\\" .. escapechar)
+end
+
+local sub_lookup = {}
+for _, ext in ipairs(settings.subtitleformats) do
+ sub_lookup[ext] = true
+end
+
+--ensures directories never accidentally end in "//" due to our added slash
+function stripdoubleslash(dir)
+ if string.sub(dir, -2) == "//" then
+ return string.sub(dir, 1, -2) --negative 2 removes the last character
+ else
+ return dir
+ end
+end
+
+function os.capture(cmd, raw)
+ local f = assert(io.popen(cmd, "r"))
+ local s = assert(f:read("*a"))
+ f:close()
+ return string.sub(s, 0, -2)
+end
+
+dir = nil
+path = nil
+cursor = 0
+length = 0
+--osd handler that displays your navigation and information
+function handler()
+ add_keybinds()
+ timer:kill()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ ass:pos(settings.text_padding_x, settings.text_padding_y)
+ ass:append(settings.style_ass_tags)
+
+ if not path then
+ if mp.get_property("path") and not settings.forcedefault then
+ --determine path from currently playing file...
+ local workingdir = mp.get_property("working-directory")
+ local playfilename = mp.get_property("filename") --just the filename, without path
+ local playpath = mp.get_property("path") --can be relative or absolute depending on what args mpv was given
+ local firstchar = string.sub(playpath, 1, 1)
+ --first we need to remove the filename (may give us empty path if mpv was started in same dir as file)
+ path = string.sub(playpath, 1, string.len(playpath) - string.len(playfilename))
+ if firstchar ~= "/" and not ON_WINDOWS then --the path of the playing file wasn't absolute, so we need to add mpv's working dir to it
+ path = workingdir .. "/" .. path
+ end
+ --now resolve that path (to resolve things like "/home/anon/Movies/../Movies/foo.mkv")
+ path = resolvedir(path)
+ --lastly, check if the folder exists, and if not then fall back to the current mpv working dir
+ if not isfolder(path) then
+ if ON_WINDOWS then
+ path = workingdir .. SEPARATOR_WINDOWS
+ else
+ path = workingdir
+ end
+ end
+ else
+ path = settings.defaultpath
+ end
+ dir, length = scandirectory(path)
+ end
+ ass:append(path .. "\\N\\N")
+ local b = cursor - math.floor(settings.visible_item_count / 2)
+ if b > 0 then
+ ass:append("...\\N")
+ end
+ if b < 0 then
+ b = 0
+ end
+ for a = b, (b + settings.visible_item_count), 1 do
+ if a == length then
+ break
+ end
+ local prefix = (a == cursor and settings.selection_prefix or settings.name_prefix)
+ ass:append(prefix .. dir[a] .. "\\N")
+ if a == (b + settings.visible_item_count) then
+ ass:append("...")
+ end
+ end
+ local w, h = mp.get_osd_size()
+ if settings.scale_by_window then
+ w, h = 0, 0
+ end
+ mp.set_osd_ass(w, h, ass.text)
+ if settings.menu_timeout then
+ timer:resume()
+ end
+end
+
+function navdown()
+ if cursor ~= length - 1 then
+ cursor = cursor + 1
+ else
+ cursor = 0
+ end
+ handler()
+end
+
+function navup()
+ if cursor ~= 0 then
+ cursor = cursor - 1
+ else
+ cursor = length - 1
+ end
+ handler()
+end
+
+--moves into selected directory, or appends to playlist incase of file
+function childdir()
+ local item = dir[cursor]
+
+ -- windows only
+ if ON_WINDOWS then
+ if WINDOWS_ROOTDIR then
+ WINDOWS_ROOTDIR = false
+ end
+ if item then
+ local newdir = utils.join_path(path, item):gsub(SEPARATOR, SEPARATOR_WINDOWS) .. SEPARATOR_WINDOWS
+ local info, error = utils.file_info(newdir)
+
+ if info and info.is_dir then
+ changepath(newdir)
+ else
+ if issubtitle(item) then
+ loadsubs(utils.join_path(path, item))
+ else
+ mp.commandv("loadfile", utils.join_path(path, item), "append-play")
+ mp.osd_message("Appended file to playlist: " .. item)
+ end
+ handler()
+ end
+ end
+
+ return
+ end
+
+ if item then
+ if isfolder(utils.join_path(path, item)) then
+ local newdir = stripdoubleslash(utils.join_path(path, dir[cursor] .. "/"))
+ changepath(newdir)
+ else
+ if issubtitle(item) then
+ loadsubs(utils.join_path(path, item))
+ else
+ mp.commandv("loadfile", utils.join_path(path, item), "append-play")
+ mp.osd_message("Appended file to playlist: " .. item)
+ end
+ handler()
+ end
+ end
+end
+
+function issubtitle(file)
+ local ext = file:match("^.+%.(.+)$")
+ return ext and sub_lookup[ext:lower()]
+end
+
+function loadsubs(file)
+ mp.commandv("sub_add", file)
+ mp.osd_message("Loaded subtitle: " .. file)
+end
+
+--replace current playlist with directory or file
+--if directory, mpv will recursively queue all items found in the directory and its subfolders
+function opendir()
+ local item = dir[cursor]
+
+ if item then
+ remove_keybinds()
+
+ local filepath = utils.join_path(path, item)
+ if ON_WINDOWS then
+ filepath = filepath:gsub(SEPARATOR, SEPARATOR_WINDOWS)
+ end
+
+ if issubtitle(item) then
+ return loadsubs(filepath)
+ end
+
+ mp.commandv("loadfile", filepath, "replace")
+ end
+end
+
+--changes the directory to the path in argument
+function changepath(args)
+ path = args
+ if WINDOWS_ROOTDIR then
+ path = WINDOWS_ROOT_DESC
+ end
+ dir, length = scandirectory(path)
+ cursor = 0
+ handler()
+end
+
+--move up to the parent directory
+function parentdir()
+ -- windows only
+ if ON_WINDOWS then
+ if path:sub(-1) == SEPARATOR_WINDOWS then
+ path = path:sub(1, -2)
+ end
+ local parent = utils.split_path(path)
+ if path == parent then
+ WINDOWS_ROOTDIR = true
+ end
+ changepath(parent)
+ return
+ end
+
+ --if path doesn't exist or can't be entered, this returns "/" (root of the drive) as the parent
+ local parent = stripdoubleslash(
+ os.capture('cd "' .. escapepath(path, '"') .. '" 2>/dev/null && cd .. 2>/dev/null && pwd') .. "/"
+ )
+
+ changepath(parent)
+end
+
+--resolves relative paths such as "/home/foo/../foo/Music" (to "/home/foo/Music") if the folder exists!
+function resolvedir(dir)
+ local safedir = escapepath(dir, '"')
+
+ -- windows only
+ if ON_WINDOWS then
+ local resolved = stripdoubleslash(os.capture('cd /d "' .. safedir .. '" && cd'))
+ return resolved .. SEPARATOR_WINDOWS
+ end
+
+ --if dir doesn't exist or can't be entered, this returns "/" (root of the drive) as the resolved path
+ local resolved = stripdoubleslash(os.capture('cd "' .. safedir .. '" 2>/dev/null && pwd') .. "/")
+ return resolved
+end
+
+--true if path exists and is a folder, otherwise false
+function isfolder(dir)
+ -- windows only
+ if ON_WINDOWS then
+ local info, error = utils.file_info(dir)
+ return info and info.is_dir or nil
+ end
+
+ local lua51returncode, _, lua52returncode = os.execute('test -d "' .. escapepath(dir, '"') .. '"')
+ return lua51returncode == 0 or lua52returncode == 0
+end
+
+function scandirectory(searchdir)
+ local directory = {}
+ --list all files, using universal utilities and flags available on both Linux and macOS
+ -- ls: -1 = list one file per line, -p = append "/" indicator to the end of directory names, -v = display in natural order
+ -- stderr messages are ignored by sending them to /dev/null
+ -- hidden files ("." prefix) are skipped, since they exist everywhere and never contain media
+ -- if we cannot list the contents (due to no permissions, etc), this returns an empty list
+
+ -- windows only
+ if ON_WINDOWS then
+ -- handle drive letters
+ if WINDOWS_ROOTDIR then
+ local popen, err = io.popen("wmic logicaldisk get caption")
+ local i = 0
+ if popen then
+ for direntry in popen:lines() do
+ -- only single letter followed by colon (:) are valid
+ if string.find(direntry, "^%a:") then
+ direntry = string.sub(direntry, 1, 2)
+ local matchedignore = false
+ for k, pattern in pairs(settings.ignorePatterns) do
+ if direntry:find(pattern) then
+ matchedignore = true
+ break --don't waste time scanning further patterns
+ end
+ end
+ if not matchedignore and not settings.ignorePaths[path .. direntry] then
+ directory[i] = direntry
+ i = i + 1
+ end
+ end
+ end
+ popen:close()
+ else
+ mp.msg.error("Could not scan for files :" .. (err or ""))
+ end
+
+ return directory, i
+ end
+
+ local i = 0
+ local files = utils.readdir(searchdir)
+
+ if not files then
+ mp.msg.error("Could not scan for files :" .. (err or ""))
+ return directory, i
+ end
+
+ for _, direntry in ipairs(files) do
+ local matchedignore = false
+ for k, pattern in pairs(settings.ignorePatterns) do
+ if direntry:find(pattern) then
+ matchedignore = true
+ break --don't waste time scanning further patterns
+ end
+ end
+ if not matchedignore and not settings.ignorePaths[path .. direntry] then
+ directory[i] = direntry
+ i = i + 1
+ end
+ end
+
+ return directory, i
+ end
+
+ local popen, err = io.popen('ls -1vp "' .. escapepath(searchdir, '"') .. '" 2>/dev/null')
+ local i = 0
+ if popen then
+ for direntry in popen:lines() do
+ local matchedignore = false
+ for k, pattern in pairs(settings.ignorePatterns) do
+ if direntry:find(pattern) then
+ matchedignore = true
+ break --don't waste time scanning further patterns
+ end
+ end
+ if not matchedignore and not settings.ignorePaths[path .. direntry] then
+ directory[i] = direntry
+ i = i + 1
+ end
+ end
+ popen:close()
+ else
+ mp.msg.error("Could not scan for files :" .. (err or ""))
+ end
+ return directory, i
+end
+
+favcursor = 1
+function cyclefavorite()
+ local firstpath = settings.favorites[1]
+ if not firstpath then
+ return
+ end
+ local favpath = nil
+ local favlen = 0
+ for key, fav in pairs(settings.favorites) do
+ favlen = favlen + 1
+ if key == favcursor then
+ favpath = fav
+ end
+ end
+ if favpath then
+ changepath(favpath)
+ favcursor = favcursor + 1
+ else
+ changepath(firstpath)
+ favcursor = 2
+ end
+end
+
+function add_keybinds()
+ mp.add_forced_key_binding(settings.key_navdown, "navdown", navdown, "repeatable")
+ mp.add_forced_key_binding(settings.key_navup, "navup", navup, "repeatable")
+ mp.add_forced_key_binding(settings.key_navopen, "navopen", opendir)
+ mp.add_forced_key_binding(settings.key_navforward, "navforward", childdir)
+ mp.add_forced_key_binding(settings.key_navback, "navback", parentdir)
+ mp.add_forced_key_binding(settings.key_navfavorites, "navfavorites", cyclefavorite)
+ mp.add_forced_key_binding(settings.key_navclose, "navclose", remove_keybinds)
+end
+
+function remove_keybinds()
+ timer:kill()
+ mp.set_osd_ass(0, 0, "")
+ if settings.dynamic_binds then
+ mp.remove_key_binding("navdown")
+ mp.remove_key_binding("navup")
+ mp.remove_key_binding("navopen")
+ mp.remove_key_binding("navforward")
+ mp.remove_key_binding("navback")
+ mp.remove_key_binding("navfavorites")
+ mp.remove_key_binding("navclose")
+ end
+end
+
+timer = mp.add_periodic_timer(settings.navigator_duration, remove_keybinds)
+timer:kill()
+
+if not settings.dynamic_binds then
+ add_keybinds()
+end
+
+active = false
+function activate()
+ if settings.menu_timeout then
+ handler()
+ else
+ if active then
+ remove_keybinds()
+ active = false
+ else
+ handler()
+ active = true
+ end
+ end
+end
+
+mp.add_key_binding(settings.navigator_mainkey, "navigator", activate)
diff --git a/ar/.config/mpv/scripts/osc-show-hide.lua b/ar/.config/mpv/scripts/osc-show-hide.lua
new file mode 100644
index 0000000..8188e9b
--- /dev/null
+++ b/ar/.config/mpv/scripts/osc-show-hide.lua
@@ -0,0 +1,40 @@
+-- osc-show-hide.lua - show or hide the on-screen controller (a script for mpv player)
+-- copyright (c) 2024 Alex Rogers <https://github.com/linguisticmind> and contributors <https://github.com/linguisticmind/mpv-scripts/graphs/contributors>
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+-- Video tutorial: https://youtu.be/Pp3a5O5OI9U&t=1m06s
+
+-- version: 0.1.1
+
+require("mp.options")
+local utils = require("mp.utils")
+
+local options = {
+ hidden_mode = "never", -- Accepted values are `'never'` or `'auto'`.
+}
+
+read_options(options, "osc_show_hide")
+
+local function osc_show_hide()
+ local visibility = utils.shared_script_property_get("osc-visibility")
+ mp.commandv(
+ "script-message",
+ "osc-visibility",
+ ((visibility == "auto" or visibility == "never") and "always" or options.hidden_mode),
+ "no-osd"
+ )
+end
+
+mp.add_key_binding("t", "osc-show-hide", osc_show_hide)
diff --git a/ar/.config/mpv/scripts/osc.lua b/ar/.config/mpv/scripts/osc.lua
new file mode 100644
index 0000000..37791b0
--- /dev/null
+++ b/ar/.config/mpv/scripts/osc.lua
@@ -0,0 +1,3109 @@
+mp.set_property("osc", "no")
+if mp.get_script_name() ~= "osc" then
+ -- reclaim osc script name after the builtin osc unloads
+ local script_path = debug.getinfo(1, "S").source:match("^@?(.*[\\/]osc%.lua)$")
+ if script_path then
+ mp.add_timeout(0.05, function()
+ mp.commandv("load-script", script_path)
+ end)
+ return
+ end
+end
+local assdraw = require 'mp.assdraw'
+local msg = require 'mp.msg'
+local opt = require 'mp.options'
+local utils = require 'mp.utils'
+
+--
+-- Parameters
+--
+-- default user option values
+-- do not touch, change them in osc.conf
+local user_opts = {
+ showwindowed = true, -- show OSC when windowed?
+ showfullscreen = true, -- show OSC when fullscreen?
+ idlescreen = true, -- show mpv logo on idle
+ scalewindowed = 1, -- scaling of the controller when windowed
+ scalefullscreen = 1, -- scaling of the controller when fullscreen
+ scaleforcedwindow = 2, -- scaling when rendered on a forced window
+ vidscale = true, -- scale the controller with the video?
+ valign = 0.8, -- vertical alignment, -1 (top) to 1 (bottom)
+ halign = 0, -- horizontal alignment, -1 (left) to 1 (right)
+ barmargin = 0, -- vertical margin of top/bottombar
+ boxalpha = 80, -- alpha of the background box,
+ -- 0 (opaque) to 255 (fully transparent)
+ hidetimeout = 500, -- duration in ms until the OSC hides if no
+ -- mouse movement. enforced non-negative for the
+ -- user, but internally negative is "always-on".
+ fadeduration = 200, -- duration of fade out in ms, 0 = no fade
+ deadzonesize = 0.5, -- size of deadzone
+ minmousemove = 0, -- minimum amount of pixels the mouse has to
+ -- move between ticks to make the OSC show up
+ iamaprogrammer = false, -- use native mpv values and disable OSC
+ -- internal track list management (and some
+ -- functions that depend on it)
+ layout = "bottombar",
+ seekbarstyle = "bar", -- bar, diamond or knob
+ seekbarhandlesize = 0.6, -- size ratio of the diamond and knob handle
+ seekrangestyle = "inverted",-- bar, line, slider, inverted or none
+ seekrangeseparate = true, -- whether the seekranges overlay on the bar-style seekbar
+ seekrangealpha = 200, -- transparency of seekranges
+ seekbarkeyframes = true, -- use keyframes when dragging the seekbar
+ scrollcontrols = true, -- allow scrolling when hovering certain OSC elements
+ title = "${media-title}", -- string compatible with property-expansion
+ -- to be shown as OSC title
+ tooltipborder = 1, -- border of tooltip in bottom/topbar
+ timetotal = false, -- display total time instead of remaining time?
+ remaining_playtime = true, -- display the remaining time in playtime or video-time mode
+ -- playtime takes speed into account, whereas video-time doesn't
+ timems = false, -- display timecodes with milliseconds?
+ tcspace = 100, -- timecode spacing (compensate font size estimation)
+ visibility = "auto", -- only used at init to set visibility_mode(...)
+ boxmaxchars = 80, -- title crop threshold for box layout
+ boxvideo = false, -- apply osc_param.video_margins to video
+ windowcontrols = "auto", -- whether to show window controls
+ windowcontrols_alignment = "right", -- which side to show window controls on
+ greenandgrumpy = false, -- disable santa hat
+ livemarkers = true, -- update seekbar chapter markers on duration change
+ chapters_osd = true, -- whether to show chapters OSD on next/prev
+ playlist_osd = true, -- whether to show playlist OSD on next/prev
+ chapter_fmt = "Chapter: %s", -- chapter print format for seekbar-hover. "no" to disable
+ unicodeminus = false, -- whether to use the Unicode minus sign character
+}
+
+-- read options from config and command-line
+opt.read_options(user_opts, "osc", function(list) update_options(list) end)
+
+local osc_param = { -- calculated by osc_init()
+ playresy = 0, -- canvas size Y
+ playresx = 0, -- canvas size X
+ display_aspect = 1,
+ unscaled_y = 0,
+ areas = {},
+ video_margins = {
+ l = 0, r = 0, t = 0, b = 0, -- left/right/top/bottom
+ },
+}
+
+local osc_styles = {
+ bigButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs50\\fnmpv-osd-symbols}",
+ smallButtonsL = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}",
+ smallButtonsLlabel = "{\\fscx105\\fscy105\\fn" .. mp.get_property("options/osd-font") .. "}",
+ smallButtonsR = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}",
+ topButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}",
+
+ elementDown = "{\\1c&H999999}",
+ timecodes = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20}",
+ vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\q2}",
+ box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
+
+ topButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\fnmpv-osd-symbols}",
+ smallButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs28\\fnmpv-osd-symbols}",
+ timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs27}",
+ timePosBar = "{\\blur0\\bord".. user_opts.tooltipborder .."\\1c&HFFFFFF\\3c&H000000\\fs30}",
+ vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}",
+
+ wcButtons = "{\\1c&HFFFFFF\\fs24\\fnmpv-osd-symbols}",
+ wcTitle = "{\\1c&HFFFFFF\\fs24\\q2}",
+ wcBar = "{\\1c&H000000}",
+}
+
+local function create_osd_overlay(...)
+ if not mp.create_osd_overlay then return end
+ return mp.create_osd_overlay(...)
+end
+
+-- internal states, do not touch
+local state = {
+ showtime, -- time of last invocation (last mouse move)
+ osc_visible = false,
+ anistart, -- time when the animation started
+ anitype, -- current type of animation
+ animation, -- current animation alpha
+ mouse_down_counter = 0, -- used for softrepeat
+ active_element = nil, -- nil = none, 0 = background, 1+ = see elements[]
+ active_event_source = nil, -- the "button" that issued the current event
+ rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time
+ tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds
+ mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs
+ initREQ = false, -- is a re-init request pending?
+ marginsREQ = false, -- is a margins update pending?
+ last_mouseX, last_mouseY, -- last mouse position, to detect significant mouse movement
+ mouse_in_window = false,
+ message_text,
+ message_hide_timer,
+ fullscreen = false,
+ tick_timer = nil,
+ tick_last_time = 0, -- when the last tick() was run
+ hide_timer = nil,
+ cache_state = nil,
+ idle = false,
+ enabled = true,
+ input_enabled = true,
+ showhide_enabled = false,
+ windowcontrols_buttons = false,
+ dmx_cache = 0,
+ using_video_margins = false,
+ border = true,
+ maximized = false,
+ osd = create_osd_overlay("ass-events"),
+ chapter_list = {}, -- sorted by time
+}
+
+local thumbfast = {
+ width = 0,
+ height = 0,
+ disabled = false
+}
+
+local window_control_box_width = 80
+local tick_delay = 0.03
+
+local is_december = os.date("*t").month == 12
+
+--
+-- Helperfunctions
+--
+
+function kill_animation()
+ state.anistart = nil
+ state.animation = nil
+ state.anitype = nil
+end
+
+function set_osd(res_x, res_y, text, z)
+ if state.osd.res_x == res_x and
+ state.osd.res_y == res_y and
+ state.osd.data == text then
+ return
+ end
+ state.osd.res_x = res_x
+ state.osd.res_y = res_y
+ state.osd.data = text
+ state.osd.z = z
+ state.osd:update()
+end
+
+set_osd = state.osd and set_osd or mp.set_osd_ass
+
+local margins_opts = {
+ {"l", "video-margin-ratio-left"},
+ {"r", "video-margin-ratio-right"},
+ {"t", "video-margin-ratio-top"},
+ {"b", "video-margin-ratio-bottom"},
+}
+
+-- scale factor for translating between real and virtual ASS coordinates
+function get_virt_scale_factor()
+ local w, h = mp.get_osd_size()
+ if w <= 0 or h <= 0 then
+ return 0, 0
+ end
+ return osc_param.playresx / w, osc_param.playresy / h
+end
+
+-- return mouse position in virtual ASS coordinates (playresx/y)
+function get_virt_mouse_pos()
+ if state.mouse_in_window then
+ local sx, sy = get_virt_scale_factor()
+ local x, y = mp.get_mouse_pos()
+ return x * sx, y * sy
+ else
+ return -1, -1
+ end
+end
+
+function set_virt_mouse_area(x0, y0, x1, y1, name)
+ local sx, sy = get_virt_scale_factor()
+ mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name)
+end
+
+function scale_value(x0, x1, y0, y1, val)
+ local m = (y1 - y0) / (x1 - x0)
+ local b = y0 - (m * x0)
+ return (m * val) + b
+end
+
+-- returns hitbox spanning coordinates (top left, bottom right corner)
+-- according to alignment
+function get_hitbox_coords(x, y, an, w, h)
+
+ local alignments = {
+ [1] = function () return x, y-h, x+w, y end,
+ [2] = function () return x-(w/2), y-h, x+(w/2), y end,
+ [3] = function () return x-w, y-h, x, y end,
+
+ [4] = function () return x, y-(h/2), x+w, y+(h/2) end,
+ [5] = function () return x-(w/2), y-(h/2), x+(w/2), y+(h/2) end,
+ [6] = function () return x-w, y-(h/2), x, y+(h/2) end,
+
+ [7] = function () return x, y, x+w, y+h end,
+ [8] = function () return x-(w/2), y, x+(w/2), y+h end,
+ [9] = function () return x-w, y, x, y+h end,
+ }
+
+ return alignments[an]()
+end
+
+function get_hitbox_coords_geo(geometry)
+ return get_hitbox_coords(geometry.x, geometry.y, geometry.an,
+ geometry.w, geometry.h)
+end
+
+function get_element_hitbox(element)
+ return element.hitbox.x1, element.hitbox.y1,
+ element.hitbox.x2, element.hitbox.y2
+end
+
+function mouse_hit(element)
+ return mouse_hit_coords(get_element_hitbox(element))
+end
+
+function mouse_hit_coords(bX1, bY1, bX2, bY2)
+ local mX, mY = get_virt_mouse_pos()
+ return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2)
+end
+
+function limit_range(min, max, val)
+ if val > max then
+ val = max
+ elseif val < min then
+ val = min
+ end
+ return val
+end
+
+-- translate value into element coordinates
+function get_slider_ele_pos_for(element, val)
+
+ local ele_pos = scale_value(
+ element.slider.min.value, element.slider.max.value,
+ element.slider.min.ele_pos, element.slider.max.ele_pos,
+ val)
+
+ return limit_range(
+ element.slider.min.ele_pos, element.slider.max.ele_pos,
+ ele_pos)
+end
+
+-- translates global (mouse) coordinates to value
+function get_slider_value_at(element, glob_pos)
+
+ local val = scale_value(
+ element.slider.min.glob_pos, element.slider.max.glob_pos,
+ element.slider.min.value, element.slider.max.value,
+ glob_pos)
+
+ return limit_range(
+ element.slider.min.value, element.slider.max.value,
+ val)
+end
+
+-- get value at current mouse position
+function get_slider_value(element)
+ return get_slider_value_at(element, get_virt_mouse_pos())
+end
+
+function countone(val)
+ if not (user_opts.iamaprogrammer) then
+ val = val + 1
+ end
+ return val
+end
+
+-- align: -1 .. +1
+-- frame: size of the containing area
+-- obj: size of the object that should be positioned inside the area
+-- margin: min. distance from object to frame (as long as -1 <= align <= +1)
+function get_align(align, frame, obj, margin)
+ return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align)
+end
+
+-- multiplies two alpha values, formular can probably be improved
+function mult_alpha(alphaA, alphaB)
+ return 255 - (((1-(alphaA/255)) * (1-(alphaB/255))) * 255)
+end
+
+function add_area(name, x1, y1, x2, y2)
+ -- create area if needed
+ if (osc_param.areas[name] == nil) then
+ osc_param.areas[name] = {}
+ end
+ table.insert(osc_param.areas[name], {x1=x1, y1=y1, x2=x2, y2=y2})
+end
+
+function ass_append_alpha(ass, alpha, modifier)
+ local ar = {}
+
+ for ai, av in pairs(alpha) do
+ av = mult_alpha(av, modifier)
+ if state.animation then
+ av = mult_alpha(av, state.animation)
+ end
+ ar[ai] = av
+ end
+
+ ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}",
+ ar[1], ar[2], ar[3], ar[4]))
+end
+
+local c = 0.551915024494 -- circle approximation
+
+function hexagon_cw(ass, x0, y0, x1, y1, r1, r2)
+ if r2 == nil then
+ r2 = r1
+ end
+ ass:move_to(x0 + r1, y0)
+ if x0 ~= x1 then
+ ass:line_to(x1 - r2, y0)
+ end
+ ass:line_to(x1, y0 + r2)
+ if x0 ~= x1 then
+ ass:line_to(x1 - r2, y1)
+ end
+ ass:line_to(x0 + r1, y1)
+ ass:line_to(x0, y0 + r1)
+end
+
+function hexagon_ccw(ass, x0, y0, x1, y1, r1, r2)
+ if r2 == nil then
+ r2 = r1
+ end
+ ass:move_to(x0 + r1, y0)
+ ass:line_to(x0, y0 + r1)
+ ass:line_to(x0 + r1, y1)
+ if x0 ~= x1 then
+ ass:line_to(x1 - r2, y1)
+ end
+ ass:line_to(x1, y0 + r2)
+ if x0 ~= x1 then
+ ass:line_to(x1 - r2, y0)
+ end
+end
+
+function round_rect_cw(ass, x0, y0, x1, y1, r1, r2)
+ if r2 == nil then
+ r2 = r1
+ end
+ local c1 = c * r1 -- circle approximation
+ local c2 = c * r2 -- circle approximation
+ ass:move_to(x0 + r1, y0)
+ ass:line_to(x1 - r2, y0) -- top line
+ if r2 > 0 then
+ ass:bezier_curve(x1 - r2 + c2, y0, x1, y0 + r2 - c2, x1, y0 + r2) -- top right corner
+ end
+ ass:line_to(x1, y1 - r2) -- right line
+ if r2 > 0 then
+ ass:bezier_curve(x1, y1 - r2 + c2, x1 - r2 + c2, y1, x1 - r2, y1) -- bottom right corner
+ end
+ ass:line_to(x0 + r1, y1) -- bottom line
+ if r1 > 0 then
+ ass:bezier_curve(x0 + r1 - c1, y1, x0, y1 - r1 + c1, x0, y1 - r1) -- bottom left corner
+ end
+ ass:line_to(x0, y0 + r1) -- left line
+ if r1 > 0 then
+ ass:bezier_curve(x0, y0 + r1 - c1, x0 + r1 - c1, y0, x0 + r1, y0) -- top left corner
+ end
+end
+
+function round_rect_ccw(ass, x0, y0, x1, y1, r1, r2)
+ if r2 == nil then
+ r2 = r1
+ end
+ local c1 = c * r1 -- circle approximation
+ local c2 = c * r2 -- circle approximation
+ ass:move_to(x0 + r1, y0)
+ if r1 > 0 then
+ ass:bezier_curve(x0 + r1 - c1, y0, x0, y0 + r1 - c1, x0, y0 + r1) -- top left corner
+ end
+ ass:line_to(x0, y1 - r1) -- left line
+ if r1 > 0 then
+ ass:bezier_curve(x0, y1 - r1 + c1, x0 + r1 - c1, y1, x0 + r1, y1) -- bottom left corner
+ end
+ ass:line_to(x1 - r2, y1) -- bottom line
+ if r2 > 0 then
+ ass:bezier_curve(x1 - r2 + c2, y1, x1, y1 - r2 + c2, x1, y1 - r2) -- bottom right corner
+ end
+ ass:line_to(x1, y0 + r2) -- right line
+ if r2 > 0 then
+ ass:bezier_curve(x1, y0 + r2 - c2, x1 - r2 + c2, y0, x1 - r2, y0) -- top right corner
+ end
+end
+
+function ass_draw_rr_h_cw(ass, x0, y0, x1, y1, r1, hexagon, r2)
+ if hexagon then
+ hexagon_cw(ass, x0, y0, x1, y1, r1, r2)
+ else
+ round_rect_cw(ass, x0, y0, x1, y1, r1, r2)
+ end
+end
+
+function ass_draw_rr_h_ccw(ass, x0, y0, x1, y1, r1, hexagon, r2)
+ if hexagon then
+ hexagon_ccw(ass, x0, y0, x1, y1, r1, r2)
+ else
+ round_rect_ccw(ass, x0, y0, x1, y1, r1, r2)
+ end
+end
+
+
+--
+-- Tracklist Management
+--
+
+local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"}
+
+-- updates the OSC internal playlists, should be run each time the track-layout changes
+function update_tracklist()
+ local tracktable = mp.get_property_native("track-list", {})
+
+ -- by osc_id
+ tracks_osc = {}
+ tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {}
+ -- by mpv_id
+ tracks_mpv = {}
+ tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {}
+ for n = 1, #tracktable do
+ if not (tracktable[n].type == "unknown") then
+ local type = tracktable[n].type
+ local mpv_id = tonumber(tracktable[n].id)
+
+ -- by osc_id
+ table.insert(tracks_osc[type], tracktable[n])
+
+ -- by mpv_id
+ tracks_mpv[type][mpv_id] = tracktable[n]
+ tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type]
+ end
+ end
+end
+
+-- return a nice list of tracks of the given type (video, audio, sub)
+function get_tracklist(type)
+ local msg = "Available " .. nicetypes[type] .. " Tracks: "
+ if not tracks_osc or #tracks_osc[type] == 0 then
+ msg = msg .. "none"
+ else
+ for n = 1, #tracks_osc[type] do
+ local track = tracks_osc[type][n]
+ local lang, title, selected = "unknown", "", "○"
+ if not(track.lang == nil) then lang = track.lang end
+ if not(track.title == nil) then title = track.title end
+ if (track.id == tonumber(mp.get_property(type))) then
+ selected = "●"
+ end
+ msg = msg.."\n"..selected.." "..n..": ["..lang.."] "..title
+ end
+ end
+ return msg
+end
+
+-- relatively change the track of given <type> by <next> tracks
+ --(+1 -> next, -1 -> previous)
+function set_track(type, next)
+ local current_track_mpv, current_track_osc
+ if (mp.get_property(type) == "no") then
+ current_track_osc = 0
+ else
+ current_track_mpv = tonumber(mp.get_property(type))
+ current_track_osc = tracks_mpv[type][current_track_mpv].osc_id
+ end
+ local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1)
+ local new_track_mpv
+ if new_track_osc == 0 then
+ new_track_mpv = "no"
+ else
+ new_track_mpv = tracks_osc[type][new_track_osc].id
+ end
+
+ mp.commandv("set", type, new_track_mpv)
+
+ if (new_track_osc == 0) then
+ show_message(nicetypes[type] .. " Track: none")
+ else
+ show_message(nicetypes[type] .. " Track: "
+ .. new_track_osc .. "/" .. #tracks_osc[type]
+ .. " [".. (tracks_osc[type][new_track_osc].lang or "unknown") .."] "
+ .. (tracks_osc[type][new_track_osc].title or ""))
+ end
+end
+
+-- get the currently selected track of <type>, OSC-style counted
+function get_track(type)
+ local track = mp.get_property(type)
+ if track ~= "no" and track ~= nil then
+ local tr = tracks_mpv[type][tonumber(track)]
+ if tr then
+ return tr.osc_id
+ end
+ end
+ return 0
+end
+
+-- WindowControl helpers
+function window_controls_enabled()
+ val = user_opts.windowcontrols
+ if val == "auto" then
+ return not state.border
+ else
+ return val ~= "no"
+ end
+end
+
+function window_controls_alignment()
+ return user_opts.windowcontrols_alignment
+end
+
+--
+-- Element Management
+--
+
+local elements = {}
+
+function prepare_elements()
+
+ -- remove elements without layout or invisible
+ local elements2 = {}
+ for n, element in pairs(elements) do
+ if not (element.layout == nil) and (element.visible) then
+ table.insert(elements2, element)
+ end
+ end
+ elements = elements2
+
+ function elem_compare (a, b)
+ return a.layout.layer < b.layout.layer
+ end
+
+ table.sort(elements, elem_compare)
+
+
+ for _,element in pairs(elements) do
+
+ local elem_geo = element.layout.geometry
+
+ -- Calculate the hitbox
+ local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo)
+ element.hitbox = {x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2}
+
+ local style_ass = assdraw.ass_new()
+
+ -- prepare static elements
+ style_ass:append("{}") -- hack to troll new_event into inserting a \n
+ style_ass:new_event()
+ style_ass:pos(elem_geo.x, elem_geo.y)
+ style_ass:an(elem_geo.an)
+ style_ass:append(element.layout.style)
+
+ element.style_ass = style_ass
+
+ local static_ass = assdraw.ass_new()
+
+
+ if (element.type == "box") then
+ --draw box
+ static_ass:draw_start()
+ ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h,
+ element.layout.box.radius, element.layout.box.hexagon)
+ static_ass:draw_stop()
+
+ elseif (element.type == "slider") then
+ --draw static slider parts
+
+ local r1 = 0
+ local r2 = 0
+ local slider_lo = element.layout.slider
+ -- offset between element outline and drag-area
+ local foV = slider_lo.border + slider_lo.gap
+
+ -- calculate positions of min and max points
+ if (slider_lo.stype ~= "bar") then
+ r1 = elem_geo.h / 2
+ element.slider.min.ele_pos = elem_geo.h / 2
+ element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2)
+ if (slider_lo.stype == "diamond") then
+ r2 = (elem_geo.h - 2 * slider_lo.border) / 2
+ elseif (slider_lo.stype == "knob") then
+ r2 = r1
+ end
+ else
+ element.slider.min.ele_pos =
+ slider_lo.border + slider_lo.gap
+ element.slider.max.ele_pos =
+ elem_geo.w - (slider_lo.border + slider_lo.gap)
+ end
+
+ element.slider.min.glob_pos =
+ element.hitbox.x1 + element.slider.min.ele_pos
+ element.slider.max.glob_pos =
+ element.hitbox.x1 + element.slider.max.ele_pos
+
+ -- -- --
+
+ static_ass:draw_start()
+
+ -- the box
+ ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h, r1, slider_lo.stype == "diamond")
+
+ -- the "hole"
+ ass_draw_rr_h_ccw(static_ass, slider_lo.border, slider_lo.border,
+ elem_geo.w - slider_lo.border, elem_geo.h - slider_lo.border,
+ r2, slider_lo.stype == "diamond")
+
+ -- marker nibbles
+ if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then
+ local markers = element.slider.markerF()
+ for _,marker in pairs(markers) do
+ if (marker > element.slider.min.value) and
+ (marker < element.slider.max.value) then
+
+ local s = get_slider_ele_pos_for(element, marker)
+
+ if (slider_lo.gap > 1) then -- draw triangles
+
+ local a = slider_lo.gap / 0.5 --0.866
+
+ --top
+ if (slider_lo.nibbles_top) then
+ static_ass:move_to(s - (a/2), slider_lo.border)
+ static_ass:line_to(s + (a/2), slider_lo.border)
+ static_ass:line_to(s, foV)
+ end
+
+ --bottom
+ if (slider_lo.nibbles_bottom) then
+ static_ass:move_to(s - (a/2),
+ elem_geo.h - slider_lo.border)
+ static_ass:line_to(s,
+ elem_geo.h - foV)
+ static_ass:line_to(s + (a/2),
+ elem_geo.h - slider_lo.border)
+ end
+
+ else -- draw 2x1px nibbles
+
+ --top
+ if (slider_lo.nibbles_top) then
+ static_ass:rect_cw(s - 1, slider_lo.border,
+ s + 1, slider_lo.border + slider_lo.gap);
+ end
+
+ --bottom
+ if (slider_lo.nibbles_bottom) then
+ static_ass:rect_cw(s - 1,
+ elem_geo.h -slider_lo.border -slider_lo.gap,
+ s + 1, elem_geo.h - slider_lo.border);
+ end
+ end
+ end
+ end
+ end
+ end
+
+ element.static_ass = static_ass
+
+
+ -- if the element is supposed to be disabled,
+ -- style it accordingly and kill the eventresponders
+ if not (element.enabled) then
+ element.layout.alpha[1] = 136
+ element.eventresponder = nil
+ end
+ end
+end
+
+
+--
+-- Element Rendering
+--
+
+-- returns nil or a chapter element from the native property chapter-list
+function get_chapter(possec)
+ local cl = state.chapter_list -- sorted, get latest before possec, if any
+
+ for n=#cl,1,-1 do
+ if possec >= cl[n].time then
+ return cl[n]
+ end
+ end
+end
+
+function render_elements(master_ass)
+
+ -- when the slider is dragged or hovered and we have a target chapter name
+ -- then we use it instead of the normal title. we calculate it before the
+ -- render iterations because the title may be rendered before the slider.
+ state.forced_title = nil
+ local se, ae = state.slider_element, elements[state.active_element]
+ if user_opts.chapter_fmt ~= "no" and se and (ae == se or (not ae and mouse_hit(se))) then
+ local dur = mp.get_property_number("duration", 0)
+ if dur > 0 then
+ local possec = get_slider_value(se) * dur / 100 -- of mouse pos
+ local ch = get_chapter(possec)
+ if ch and ch.title and ch.title ~= "" then
+ state.forced_title = string.format(user_opts.chapter_fmt, ch.title)
+ end
+ end
+ end
+
+ for n=1, #elements do
+ local element = elements[n]
+
+ local style_ass = assdraw.ass_new()
+ style_ass:merge(element.style_ass)
+ ass_append_alpha(style_ass, element.layout.alpha, 0)
+
+ if element.eventresponder and (state.active_element == n) then
+
+ -- run render event functions
+ if not (element.eventresponder.render == nil) then
+ element.eventresponder.render(element)
+ end
+
+ if mouse_hit(element) then
+ -- mouse down styling
+ if (element.styledown) then
+ style_ass:append(osc_styles.elementDown)
+ end
+
+ if (element.softrepeat) and (state.mouse_down_counter >= 15
+ and state.mouse_down_counter % 5 == 0) then
+
+ element.eventresponder[state.active_event_source.."_down"](element)
+ end
+ state.mouse_down_counter = state.mouse_down_counter + 1
+ end
+
+ end
+
+ local elem_ass = assdraw.ass_new()
+
+ elem_ass:merge(style_ass)
+
+ if not (element.type == "button") then
+ elem_ass:merge(element.static_ass)
+ end
+
+ if (element.type == "slider") then
+
+ local slider_lo = element.layout.slider
+ local elem_geo = element.layout.geometry
+ local s_min = element.slider.min.value
+ local s_max = element.slider.max.value
+
+ -- draw pos marker
+ local foH, xp
+ local pos = element.slider.posF()
+ local foV = slider_lo.border + slider_lo.gap
+ local innerH = elem_geo.h - (2 * foV)
+ local seekRanges = element.slider.seekRangesF()
+ local seekRangeLineHeight = innerH / 5
+
+ if slider_lo.stype ~= "bar" then
+ foH = elem_geo.h / 2
+ else
+ foH = slider_lo.border + slider_lo.gap
+ end
+
+ if pos then
+ xp = get_slider_ele_pos_for(element, pos)
+
+ if slider_lo.stype ~= "bar" then
+ local r = (user_opts.seekbarhandlesize * innerH) / 2
+ ass_draw_rr_h_cw(elem_ass, xp - r, foH - r,
+ xp + r, foH + r,
+ r, slider_lo.stype == "diamond")
+ else
+ local h = 0
+ if seekRanges and user_opts.seekrangeseparate and slider_lo.rtype ~= "inverted" then
+ h = seekRangeLineHeight
+ end
+ elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV - h)
+
+ if seekRanges and not user_opts.seekrangeseparate and slider_lo.rtype ~= "inverted" then
+ -- Punch holes for the seekRanges to be drawn later
+ for _,range in pairs(seekRanges) do
+ if range["start"] < pos then
+ local pstart = get_slider_ele_pos_for(element, range["start"])
+ local pend = xp
+
+ if pos > range["end"] then
+ pend = get_slider_ele_pos_for(element, range["end"])
+ end
+ elem_ass:rect_ccw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV)
+ end
+ end
+ end
+ end
+
+ if slider_lo.rtype == "slider" then
+ ass_draw_rr_h_cw(elem_ass, foH - innerH / 6, foH - innerH / 6,
+ xp, foH + innerH / 6,
+ innerH / 6, slider_lo.stype == "diamond", 0)
+ ass_draw_rr_h_cw(elem_ass, xp, foH - innerH / 15,
+ elem_geo.w - foH + innerH / 15, foH + innerH / 15,
+ 0, slider_lo.stype == "diamond", innerH / 15)
+ for _,range in pairs(seekRanges or {}) do
+ local pstart = get_slider_ele_pos_for(element, range["start"])
+ local pend = get_slider_ele_pos_for(element, range["end"])
+ ass_draw_rr_h_ccw(elem_ass, pstart, foH - innerH / 21,
+ pend, foH + innerH / 21,
+ innerH / 21, slider_lo.stype == "diamond")
+ end
+ end
+ end
+
+ if seekRanges then
+ if slider_lo.rtype ~= "inverted" then
+ elem_ass:draw_stop()
+ elem_ass:merge(element.style_ass)
+ ass_append_alpha(elem_ass, element.layout.alpha, user_opts.seekrangealpha)
+ elem_ass:merge(element.static_ass)
+ end
+
+ for _,range in pairs(seekRanges) do
+ local pstart = get_slider_ele_pos_for(element, range["start"])
+ local pend = get_slider_ele_pos_for(element, range["end"])
+
+ if slider_lo.rtype == "slider" then
+ ass_draw_rr_h_cw(elem_ass, pstart, foH - innerH / 21,
+ pend, foH + innerH / 21,
+ innerH / 21, slider_lo.stype == "diamond")
+ elseif slider_lo.rtype == "line" then
+ if slider_lo.stype == "bar" then
+ elem_ass:rect_cw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV)
+ else
+ ass_draw_rr_h_cw(elem_ass, pstart - innerH / 8, foH - innerH / 8,
+ pend + innerH / 8, foH + innerH / 8,
+ innerH / 8, slider_lo.stype == "diamond")
+ end
+ elseif slider_lo.rtype == "bar" then
+ if slider_lo.stype ~= "bar" then
+ ass_draw_rr_h_cw(elem_ass, pstart - innerH / 2, foV,
+ pend + innerH / 2, foV + innerH,
+ innerH / 2, slider_lo.stype == "diamond")
+ elseif range["end"] >= (pos or 0) then
+ elem_ass:rect_cw(pstart, foV, pend, elem_geo.h - foV)
+ else
+ elem_ass:rect_cw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV)
+ end
+ elseif slider_lo.rtype == "inverted" then
+ if slider_lo.stype ~= "bar" then
+ ass_draw_rr_h_ccw(elem_ass, pstart, (elem_geo.h / 2) - 1, pend,
+ (elem_geo.h / 2) + 1,
+ 1, slider_lo.stype == "diamond")
+ else
+ elem_ass:rect_ccw(pstart, (elem_geo.h / 2) - 1, pend, (elem_geo.h / 2) + 1)
+ end
+ end
+ end
+ end
+
+ elem_ass:draw_stop()
+
+ -- add tooltip
+ if not (element.slider.tooltipF == nil) then
+
+ if mouse_hit(element) then
+ local sliderpos = get_slider_value(element)
+ local tooltiplabel = element.slider.tooltipF(sliderpos)
+
+ local an = slider_lo.tooltip_an
+
+ local ty
+
+ if (an == 2) then
+ ty = element.hitbox.y1 - slider_lo.border
+ else
+ ty = element.hitbox.y1 + elem_geo.h/2
+ end
+
+ local tx = get_virt_mouse_pos()
+ local thumb_tx = tx
+ if (slider_lo.adjust_tooltip) then
+ if (an == 2) then
+ if (sliderpos < (s_min + 3)) then
+ an = an - 1
+ elseif (sliderpos > (s_max - 3)) then
+ an = an + 1
+ end
+ elseif (sliderpos > (s_max+s_min)/2) then
+ an = an + 1
+ tx = tx - 5
+ else
+ an = an - 1
+ tx = tx + 10
+ end
+ end
+
+ -- tooltip label
+ elem_ass:new_event()
+ elem_ass:pos(tx, ty)
+ elem_ass:an(an)
+ elem_ass:append(slider_lo.tooltip_style)
+ ass_append_alpha(elem_ass, slider_lo.alpha, 0)
+ elem_ass:append(tooltiplabel)
+
+ -- thumbnail
+ if not thumbfast.disabled and thumbfast.width ~= 0 and thumbfast.height ~= 0 then
+ local osd_w = mp.get_property_number("osd-width")
+ if osd_w then
+ local r_w, r_h = get_virt_scale_factor()
+
+ local tooltip_font_size = (user_opts.layout == "box" or user_opts.layout == "slimbox") and 2 or 12
+ local thumb_ty = user_opts.layout ~= "topbar" and element.hitbox.y1 - 8 or element.hitbox.y2 + tooltip_font_size + 8
+
+ local thumb_pad = 2
+ local thumb_margin_x = 20 / r_w
+ local thumb_margin_y = (4 + user_opts.tooltipborder) / r_h + thumb_pad
+ local thumb_x = math.min(osd_w - thumbfast.width - thumb_margin_x, math.max(thumb_margin_x, thumb_tx / r_w - thumbfast.width / 2))
+ local thumb_y = user_opts.layout ~= "topbar" and thumb_ty / r_h - thumbfast.height - tooltip_font_size / r_h - thumb_margin_y or thumb_ty / r_h + thumb_margin_y
+
+ thumb_x = math.floor(thumb_x + 0.5)
+ thumb_y = math.floor(thumb_y + 0.5)
+
+ elem_ass:new_event()
+ elem_ass:pos(thumb_x * r_w, thumb_y * r_h)
+ elem_ass:an(7)
+ elem_ass:append(osc_styles.timePosBar)
+ elem_ass:append("{\\1a&H20&}")
+ elem_ass:draw_start()
+ elem_ass:rect_cw(-thumb_pad * r_w, -thumb_pad * r_h, (thumbfast.width + thumb_pad) * r_w, (thumbfast.height + thumb_pad) * r_h)
+ elem_ass:draw_stop()
+
+ mp.commandv("script-message-to", "thumbfast", "thumb",
+ mp.get_property_number("duration", 0) * (sliderpos / 100),
+ thumb_x,
+ thumb_y
+ )
+ end
+ end
+ else
+ if thumbfast.width ~= 0 and thumbfast.height ~= 0 then
+ mp.commandv("script-message-to", "thumbfast", "clear")
+ end
+ end
+ end
+
+ elseif (element.type == "button") then
+
+ local buttontext
+ if type(element.content) == "function" then
+ buttontext = element.content() -- function objects
+ elseif not (element.content == nil) then
+ buttontext = element.content -- text objects
+ end
+
+ local maxchars = element.layout.button.maxchars
+ if not (maxchars == nil) and (#buttontext > maxchars) then
+ local max_ratio = 1.25 -- up to 25% more chars while shrinking
+ local limit = math.max(0, math.floor(maxchars * max_ratio) - 3)
+ if (#buttontext > limit) then
+ while (#buttontext > limit) do
+ buttontext = buttontext:gsub(".[\128-\191]*$", "")
+ end
+ buttontext = buttontext .. "..."
+ end
+ local _, nchars2 = buttontext:gsub(".[\128-\191]*", "")
+ local stretch = (maxchars/#buttontext)*100
+ buttontext = string.format("{\\fscx%f}",
+ (maxchars/#buttontext)*100) .. buttontext
+ end
+
+ elem_ass:append(buttontext)
+ end
+
+ master_ass:merge(elem_ass)
+ end
+end
+
+--
+-- Message display
+--
+
+-- pos is 1 based
+function limited_list(prop, pos)
+ local proplist = mp.get_property_native(prop, {})
+ local count = #proplist
+ if count == 0 then
+ return count, proplist
+ end
+
+ local fs = tonumber(mp.get_property('options/osd-font-size'))
+ local max = math.ceil(osc_param.unscaled_y*0.75 / fs)
+ if max % 2 == 0 then
+ max = max - 1
+ end
+ local delta = math.ceil(max / 2) - 1
+ local begi = math.max(math.min(pos - delta, count - max + 1), 1)
+ local endi = math.min(begi + max - 1, count)
+
+ local reslist = {}
+ for i=begi, endi do
+ local item = proplist[i]
+ item.current = (i == pos) and true or nil
+ table.insert(reslist, item)
+ end
+ return count, reslist
+end
+
+function get_playlist()
+ local pos = mp.get_property_number('playlist-pos', 0) + 1
+ local count, limlist = limited_list('playlist', pos)
+ if count == 0 then
+ return 'Empty playlist.'
+ end
+
+ local message = string.format('Playlist [%d/%d]:\n', pos, count)
+ for i, v in ipairs(limlist) do
+ local title = v.title
+ local _, filename = utils.split_path(v.filename)
+ if title == nil then
+ title = filename
+ end
+ message = string.format('%s %s %s\n', message,
+ (v.current and '●' or '○'), title)
+ end
+ return message
+end
+
+function get_chapterlist()
+ local pos = mp.get_property_number('chapter', 0) + 1
+ local count, limlist = limited_list('chapter-list', pos)
+ if count == 0 then
+ return 'No chapters.'
+ end
+
+ local message = string.format('Chapters [%d/%d]:\n', pos, count)
+ for i, v in ipairs(limlist) do
+ local time = mp.format_time(v.time)
+ local title = v.title
+ if title == nil then
+ title = string.format('Chapter %02d', i)
+ end
+ message = string.format('%s[%s] %s %s\n', message, time,
+ (v.current and '●' or '○'), title)
+ end
+ return message
+end
+
+function show_message(text, duration)
+
+ --print("text: "..text.." duration: " .. duration)
+ if duration == nil then
+ duration = tonumber(mp.get_property("options/osd-duration")) / 1000
+ elseif not type(duration) == "number" then
+ print("duration: " .. duration)
+ end
+
+ -- cut the text short, otherwise the following functions
+ -- may slow down massively on huge input
+ text = string.sub(text, 0, 4000)
+
+ -- replace actual linebreaks with ASS linebreaks
+ text = string.gsub(text, "\n", "\\N")
+
+ state.message_text = text
+
+ if not state.message_hide_timer then
+ state.message_hide_timer = mp.add_timeout(0, request_tick)
+ end
+ state.message_hide_timer:kill()
+ state.message_hide_timer.timeout = duration
+ state.message_hide_timer:resume()
+ request_tick()
+end
+
+function render_message(ass)
+ if state.message_hide_timer and state.message_hide_timer:is_enabled() and
+ state.message_text
+ then
+ local _, lines = string.gsub(state.message_text, "\\N", "")
+
+ local fontsize = tonumber(mp.get_property("options/osd-font-size"))
+ local outline = tonumber(mp.get_property("options/osd-border-size"))
+ local maxlines = math.ceil(osc_param.unscaled_y*0.75 / fontsize)
+ local counterscale = osc_param.playresy / osc_param.unscaled_y
+
+ fontsize = fontsize * counterscale / math.max(0.65 + math.min(lines/maxlines, 1), 1)
+ outline = outline * counterscale / math.max(0.75 + math.min(lines/maxlines, 1)/2, 1)
+
+ local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}"
+
+
+ ass:new_event()
+ ass:append(style .. state.message_text)
+ else
+ state.message_text = nil
+ end
+end
+
+--
+-- Initialisation and Layout
+--
+
+function new_element(name, type)
+ elements[name] = {}
+ elements[name].type = type
+
+ -- add default stuff
+ elements[name].eventresponder = {}
+ elements[name].visible = true
+ elements[name].enabled = true
+ elements[name].softrepeat = false
+ elements[name].styledown = (type == "button")
+ elements[name].state = {}
+
+ if (type == "slider") then
+ elements[name].slider = {min = {value = 0}, max = {value = 100}}
+ end
+
+
+ return elements[name]
+end
+
+function add_layout(name)
+ if not (elements[name] == nil) then
+ -- new layout
+ elements[name].layout = {}
+
+ -- set layout defaults
+ elements[name].layout.layer = 50
+ elements[name].layout.alpha = {[1] = 0, [2] = 255, [3] = 255, [4] = 255}
+
+ if (elements[name].type == "button") then
+ elements[name].layout.button = {
+ maxchars = nil,
+ }
+ elseif (elements[name].type == "slider") then
+ -- slider defaults
+ elements[name].layout.slider = {
+ border = 1,
+ gap = 1,
+ nibbles_top = true,
+ nibbles_bottom = true,
+ stype = "slider",
+ adjust_tooltip = true,
+ tooltip_style = "",
+ tooltip_an = 2,
+ alpha = {[1] = 0, [2] = 255, [3] = 88, [4] = 255},
+ }
+ elseif (elements[name].type == "box") then
+ elements[name].layout.box = {radius = 0, hexagon = false}
+ end
+
+ return elements[name].layout
+ else
+ msg.error("Can't add_layout to element \""..name.."\", doesn't exist.")
+ end
+end
+
+-- Window Controls
+function window_controls(topbar)
+ local wc_geo = {
+ x = 0,
+ y = 30 + user_opts.barmargin,
+ an = 1,
+ w = osc_param.playresx,
+ h = 30,
+ }
+
+ local alignment = window_controls_alignment()
+ local controlbox_w = window_control_box_width
+ local titlebox_w = wc_geo.w - controlbox_w
+
+ -- Default alignment is "right"
+ local controlbox_left = wc_geo.w - controlbox_w
+ local titlebox_left = wc_geo.x
+ local titlebox_right = wc_geo.w - controlbox_w
+
+ if alignment == "left" then
+ controlbox_left = wc_geo.x
+ titlebox_left = wc_geo.x + controlbox_w
+ titlebox_right = wc_geo.w
+ end
+
+ add_area("window-controls",
+ get_hitbox_coords(controlbox_left, wc_geo.y, wc_geo.an,
+ controlbox_w, wc_geo.h))
+
+ local lo
+
+ -- Background Bar
+ new_element("wcbar", "box")
+ lo = add_layout("wcbar")
+ lo.geometry = wc_geo
+ lo.layer = 10
+ lo.style = osc_styles.wcBar
+ lo.alpha[1] = user_opts.boxalpha
+
+ local button_y = wc_geo.y - (wc_geo.h / 2)
+ local first_geo =
+ {x = controlbox_left + 5, y = button_y, an = 4, w = 25, h = 25}
+ local second_geo =
+ {x = controlbox_left + 30, y = button_y, an = 4, w = 25, h = 25}
+ local third_geo =
+ {x = controlbox_left + 55, y = button_y, an = 4, w = 25, h = 25}
+
+ -- Window control buttons use symbols in the custom mpv osd font
+ -- because the official unicode codepoints are sufficiently
+ -- exotic that a system might lack an installed font with them,
+ -- and libass will complain that they are not present in the
+ -- default font, even if another font with them is available.
+
+ -- Close: 🗙
+ ne = new_element("close", "button")
+ ne.content = "\238\132\149"
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("quit") end
+ lo = add_layout("close")
+ lo.geometry = alignment == "left" and first_geo or third_geo
+ lo.style = osc_styles.wcButtons
+
+ -- Minimize: 🗕
+ ne = new_element("minimize", "button")
+ ne.content = "\238\132\146"
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "window-minimized") end
+ lo = add_layout("minimize")
+ lo.geometry = alignment == "left" and second_geo or first_geo
+ lo.style = osc_styles.wcButtons
+
+ -- Maximize: 🗖 /🗗
+ ne = new_element("maximize", "button")
+ if state.maximized or state.fullscreen then
+ ne.content = "\238\132\148"
+ else
+ ne.content = "\238\132\147"
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ if state.fullscreen then
+ mp.commandv("cycle", "fullscreen")
+ else
+ mp.commandv("cycle", "window-maximized")
+ end
+ end
+ lo = add_layout("maximize")
+ lo.geometry = alignment == "left" and third_geo or second_geo
+ lo.style = osc_styles.wcButtons
+
+ -- deadzone below window controls
+ local sh_area_y0, sh_area_y1
+ sh_area_y0 = user_opts.barmargin
+ sh_area_y1 = wc_geo.y + get_align(1 - (2 * user_opts.deadzonesize),
+ osc_param.playresy - wc_geo.y, 0, 0)
+ add_area("showhide_wc", wc_geo.x, sh_area_y0, wc_geo.w, sh_area_y1)
+
+ if topbar then
+ -- The title is already there as part of the top bar
+ return
+ else
+ -- Apply boxvideo margins to the control bar
+ osc_param.video_margins.t = wc_geo.h / osc_param.playresy
+ end
+
+ -- Window Title
+ ne = new_element("wctitle", "button")
+ ne.content = function ()
+ local title = mp.command_native({"expand-text", user_opts.title})
+ -- escape ASS, and strip newlines and trailing slashes
+ title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{")
+ return not (title == "") and title or "mpv"
+ end
+ local left_pad = 5
+ local right_pad = 10
+ lo = add_layout("wctitle")
+ lo.geometry =
+ { x = titlebox_left + left_pad, y = wc_geo.y - 3, an = 1,
+ w = titlebox_w, h = wc_geo.h }
+ lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
+ osc_styles.wcTitle,
+ titlebox_left + left_pad, wc_geo.y - wc_geo.h,
+ titlebox_right - right_pad , wc_geo.y + wc_geo.h)
+
+ add_area("window-controls-title",
+ titlebox_left, 0, titlebox_right, wc_geo.h)
+end
+
+--
+-- Layouts
+--
+
+local layouts = {}
+
+-- Classic box layout
+layouts["box"] = function ()
+
+ local osc_geo = {
+ w = 550, -- width
+ h = 138, -- height
+ r = 10, -- corner-radius
+ p = 15, -- padding
+ }
+
+ -- make sure the OSC actually fits into the video
+ if (osc_param.playresx < (osc_geo.w + (2 * osc_geo.p))) then
+ osc_param.playresy = (osc_geo.w+(2*osc_geo.p))/osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ end
+
+ -- position of the controller according to video aspect and valignment
+ local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
+ osc_geo.w, 0))
+ local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
+ osc_geo.h, 0))
+
+ -- position offset for contents aligned at the borders of the box
+ local pos_offsetX = (osc_geo.w - (2*osc_geo.p)) / 2
+ local pos_offsetY = (osc_geo.h - (2*osc_geo.p)) / 2
+
+ osc_param.areas = {} -- delete areas
+
+ -- area for active mouse input
+ add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
+
+ -- area for show/hide
+ local sh_area_y0, sh_area_y1
+ if user_opts.valign > 0 then
+ -- deadzone above OSC
+ sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
+ posY - (osc_geo.h / 2), 0, 0)
+ sh_area_y1 = osc_param.playresy
+ else
+ -- deadzone below OSC
+ sh_area_y0 = 0
+ sh_area_y1 = (posY + (osc_geo.h / 2)) +
+ get_align(1 - (2*user_opts.deadzonesize),
+ osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
+ end
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ -- fetch values
+ local osc_w, osc_h, osc_r, osc_p =
+ osc_geo.w, osc_geo.h, osc_geo.r, osc_geo.p
+
+ local lo
+
+ --
+ -- Background box
+ --
+
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = {x = posX, y = posY, an = 5, w = osc_w, h = osc_h}
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+ lo.alpha[3] = user_opts.boxalpha
+ lo.box.radius = osc_r
+
+ --
+ -- Title row
+ --
+
+ local titlerowY = posY - pos_offsetY - 10
+
+ lo = add_layout("title")
+ lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12}
+ lo.style = osc_styles.vidtitle
+ lo.button.maxchars = user_opts.boxmaxchars
+
+ lo = add_layout("pl_prev")
+ lo.geometry =
+ {x = (posX - pos_offsetX), y = titlerowY, an = 7, w = 12, h = 12}
+ lo.style = osc_styles.topButtons
+
+ lo = add_layout("pl_next")
+ lo.geometry =
+ {x = (posX + pos_offsetX), y = titlerowY, an = 9, w = 12, h = 12}
+ lo.style = osc_styles.topButtons
+
+ --
+ -- Big buttons
+ --
+
+ local bigbtnrowY = posY - pos_offsetY + 35
+ local bigbtndist = 60
+
+ lo = add_layout("playpause")
+ lo.geometry =
+ {x = posX, y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("skipback")
+ lo.geometry =
+ {x = posX - bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("skipfrwd")
+ lo.geometry =
+ {x = posX + bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("ch_prev")
+ lo.geometry =
+ {x = posX - (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("ch_next")
+ lo.geometry =
+ {x = posX + (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("cy_audio")
+ lo.geometry =
+ {x = posX - pos_offsetX, y = bigbtnrowY, an = 1, w = 70, h = 18}
+ lo.style = osc_styles.smallButtonsL
+
+ lo = add_layout("cy_sub")
+ lo.geometry =
+ {x = posX - pos_offsetX, y = bigbtnrowY, an = 7, w = 70, h = 18}
+ lo.style = osc_styles.smallButtonsL
+
+ lo = add_layout("tog_fs")
+ lo.geometry =
+ {x = posX+pos_offsetX - 25, y = bigbtnrowY, an = 4, w = 25, h = 25}
+ lo.style = osc_styles.smallButtonsR
+
+ lo = add_layout("volume")
+ lo.geometry =
+ {x = posX+pos_offsetX - (25 * 2) - osc_geo.p,
+ y = bigbtnrowY, an = 4, w = 25, h = 25}
+ lo.style = osc_styles.smallButtonsR
+
+ --
+ -- Seekbar
+ --
+
+ lo = add_layout("seekbar")
+ lo.geometry =
+ {x = posX, y = posY+pos_offsetY-22, an = 2, w = pos_offsetX*2, h = 15}
+ lo.style = osc_styles.timecodes
+ lo.slider.tooltip_style = osc_styles.vidtitle
+ lo.slider.stype = user_opts["seekbarstyle"]
+ lo.slider.rtype = user_opts["seekrangestyle"]
+
+ --
+ -- Timecodes + Cache
+ --
+
+ local bottomrowY = posY + pos_offsetY - 5
+
+ lo = add_layout("tc_left")
+ lo.geometry =
+ {x = posX - pos_offsetX, y = bottomrowY, an = 4, w = 110, h = 18}
+ lo.style = osc_styles.timecodes
+
+ lo = add_layout("tc_right")
+ lo.geometry =
+ {x = posX + pos_offsetX, y = bottomrowY, an = 6, w = 110, h = 18}
+ lo.style = osc_styles.timecodes
+
+ lo = add_layout("cache")
+ lo.geometry =
+ {x = posX, y = bottomrowY, an = 5, w = 110, h = 18}
+ lo.style = osc_styles.timecodes
+
+end
+
+-- slim box layout
+layouts["slimbox"] = function ()
+
+ local osc_geo = {
+ w = 660, -- width
+ h = 70, -- height
+ r = 10, -- corner-radius
+ }
+
+ -- make sure the OSC actually fits into the video
+ if (osc_param.playresx < (osc_geo.w)) then
+ osc_param.playresy = (osc_geo.w)/osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ end
+
+ -- position of the controller according to video aspect and valignment
+ local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
+ osc_geo.w, 0))
+ local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
+ osc_geo.h, 0))
+
+ osc_param.areas = {} -- delete areas
+
+ -- area for active mouse input
+ add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
+
+ -- area for show/hide
+ local sh_area_y0, sh_area_y1
+ if user_opts.valign > 0 then
+ -- deadzone above OSC
+ sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
+ posY - (osc_geo.h / 2), 0, 0)
+ sh_area_y1 = osc_param.playresy
+ else
+ -- deadzone below OSC
+ sh_area_y0 = 0
+ sh_area_y1 = (posY + (osc_geo.h / 2)) +
+ get_align(1 - (2*user_opts.deadzonesize),
+ osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
+ end
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ local lo
+
+ local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100
+
+ -- styles
+ local styles = {
+ box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
+ timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}",
+ tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}",
+ }
+
+
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+ lo.alpha[3] = 0
+ if not (user_opts["seekbarstyle"] == "bar") then
+ lo.box.radius = osc_geo.r
+ lo.box.hexagon = user_opts["seekbarstyle"] == "diamond"
+ end
+
+
+ lo = add_layout("seekbar")
+ lo.geometry =
+ {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
+ lo.style = osc_styles.timecodes
+ lo.slider.border = 0
+ lo.slider.gap = 1.5
+ lo.slider.tooltip_style = styles.tooltip
+ lo.slider.stype = user_opts["seekbarstyle"]
+ lo.slider.rtype = user_opts["seekrangestyle"]
+ lo.slider.adjust_tooltip = false
+
+ --
+ -- Timecodes
+ --
+
+ lo = add_layout("tc_left")
+ lo.geometry =
+ {x = posX - (inner_w/2) + osc_geo.r, y = posY + 1,
+ an = 7, w = tc_w, h = ele_h}
+ lo.style = styles.timecodes
+ lo.alpha[3] = user_opts.boxalpha
+
+ lo = add_layout("tc_right")
+ lo.geometry =
+ {x = posX + (inner_w/2) - osc_geo.r, y = posY + 1,
+ an = 9, w = tc_w, h = ele_h}
+ lo.style = styles.timecodes
+ lo.alpha[3] = user_opts.boxalpha
+
+ -- Cache
+
+ lo = add_layout("cache")
+ lo.geometry =
+ {x = posX, y = posY + 1,
+ an = 8, w = tc_w, h = ele_h}
+ lo.style = styles.timecodes
+ lo.alpha[3] = user_opts.boxalpha
+
+
+end
+
+function bar_layout(direction)
+ local osc_geo = {
+ x = -2,
+ y,
+ an = (direction < 0) and 7 or 1,
+ w,
+ h = 56,
+ }
+
+ local padX = 9
+ local padY = 3
+ local buttonW = 27
+ local tcW = (state.tc_ms) and 170 or 110
+ if user_opts.tcspace >= 50 and user_opts.tcspace <= 200 then
+ -- adjust our hardcoded font size estimation
+ tcW = tcW * user_opts.tcspace / 100
+ end
+
+ local tsW = 90
+ local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2
+
+ -- Special topbar handling when window controls are present
+ local padwc_l
+ local padwc_r
+ if direction < 0 or not window_controls_enabled() then
+ padwc_l = 0
+ padwc_r = 0
+ elseif window_controls_alignment() == "left" then
+ padwc_l = window_control_box_width
+ padwc_r = 0
+ else
+ padwc_l = 0
+ padwc_r = window_control_box_width
+ end
+
+ if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
+ osc_param.playresy = minW / osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ end
+
+ osc_geo.y = direction * (54 + user_opts.barmargin)
+ osc_geo.w = osc_param.playresx + 4
+ if direction < 0 then
+ osc_geo.y = osc_geo.y + osc_param.playresy
+ end
+
+ local line1 = osc_geo.y - direction * (9 + padY)
+ local line2 = osc_geo.y - direction * (36 + padY)
+
+ osc_param.areas = {}
+
+ add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
+ osc_geo.w, osc_geo.h))
+
+ local sh_area_y0, sh_area_y1
+ if direction > 0 then
+ -- deadzone below OSC
+ sh_area_y0 = user_opts.barmargin
+ sh_area_y1 = osc_geo.y + get_align(1 - (2 * user_opts.deadzonesize),
+ osc_param.playresy - osc_geo.y, 0, 0)
+ else
+ -- deadzone above OSC
+ sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize), osc_geo.y, 0, 0)
+ sh_area_y1 = osc_param.playresy - user_opts.barmargin
+ end
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ local lo, geo
+
+ -- Background bar
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = osc_geo
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+
+
+ -- Playlist prev/next
+ geo = { x = osc_geo.x + padX, y = line1,
+ an = 4, w = 18, h = 18 - padY }
+ lo = add_layout("pl_prev")
+ lo.geometry = geo
+ lo.style = osc_styles.topButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("pl_next")
+ lo.geometry = geo
+ lo.style = osc_styles.topButtonsBar
+
+ local t_l = geo.x + geo.w + padX
+
+ -- Cache
+ geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y,
+ an = 6, w = 150, h = geo.h }
+ lo = add_layout("cache")
+ lo.geometry = geo
+ lo.style = osc_styles.vidtitleBar
+
+ local t_r = geo.x - geo.w - padX*2
+
+ -- Title
+ geo = { x = t_l, y = geo.y, an = 4,
+ w = t_r - t_l, h = geo.h }
+ lo = add_layout("title")
+ lo.geometry = geo
+ lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
+ osc_styles.vidtitleBar,
+ geo.x, geo.y-geo.h, geo.w, geo.y+geo.h)
+
+
+ -- Playback control buttons
+ geo = { x = osc_geo.x + padX + padwc_l, y = line2, an = 4,
+ w = buttonW, h = 36 - padY*2}
+ lo = add_layout("playpause")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("ch_prev")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("ch_next")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Left timecode
+ geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6,
+ w = tcW, h = geo.h }
+ lo = add_layout("tc_left")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+
+ local sb_l = geo.x + padX
+
+ -- Fullscreen button
+ geo = { x = osc_geo.x + osc_geo.w - buttonW - padX - padwc_r, y = geo.y, an = 4,
+ w = buttonW, h = geo.h }
+ lo = add_layout("tog_fs")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Volume
+ geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("volume")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Track selection buttons
+ geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
+ lo = add_layout("cy_sub")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("cy_audio")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+
+ -- Right timecode
+ geo = { x = geo.x - padX - tcW - 10, y = geo.y, an = geo.an,
+ w = tcW, h = geo.h }
+ lo = add_layout("tc_right")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+
+ local sb_r = geo.x - padX
+
+
+ -- Seekbar
+ geo = { x = sb_l, y = geo.y, an = geo.an,
+ w = math.max(0, sb_r - sb_l), h = geo.h }
+ new_element("bgbar1", "box")
+ lo = add_layout("bgbar1")
+
+ lo.geometry = geo
+ lo.layer = 15
+ lo.style = osc_styles.timecodesBar
+ lo.alpha[1] =
+ math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.8)
+ if not (user_opts["seekbarstyle"] == "bar") then
+ lo.box.radius = geo.h / 2
+ lo.box.hexagon = user_opts["seekbarstyle"] == "diamond"
+ end
+
+ lo = add_layout("seekbar")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+ lo.slider.border = 0
+ lo.slider.gap = 2
+ lo.slider.tooltip_style = osc_styles.timePosBar
+ lo.slider.tooltip_an = 5
+ lo.slider.stype = user_opts["seekbarstyle"]
+ lo.slider.rtype = user_opts["seekrangestyle"]
+
+ if direction < 0 then
+ osc_param.video_margins.b = osc_geo.h / osc_param.playresy
+ else
+ osc_param.video_margins.t = osc_geo.h / osc_param.playresy
+ end
+end
+
+layouts["bottombar"] = function()
+ bar_layout(-1)
+end
+
+layouts["topbar"] = function()
+ bar_layout(1)
+end
+
+-- Validate string type user options
+function validate_user_opts()
+ if layouts[user_opts.layout] == nil then
+ msg.warn("Invalid setting \""..user_opts.layout.."\" for layout")
+ user_opts.layout = "bottombar"
+ end
+
+ if user_opts.seekbarstyle ~= "bar" and
+ user_opts.seekbarstyle ~= "diamond" and
+ user_opts.seekbarstyle ~= "knob" then
+ msg.warn("Invalid setting \"" .. user_opts.seekbarstyle
+ .. "\" for seekbarstyle")
+ user_opts.seekbarstyle = "bar"
+ end
+
+ if user_opts.seekrangestyle ~= "bar" and
+ user_opts.seekrangestyle ~= "line" and
+ user_opts.seekrangestyle ~= "slider" and
+ user_opts.seekrangestyle ~= "inverted" and
+ user_opts.seekrangestyle ~= "none" then
+ msg.warn("Invalid setting \"" .. user_opts.seekrangestyle
+ .. "\" for seekrangestyle")
+ user_opts.seekrangestyle = "inverted"
+ end
+
+ if user_opts.seekrangestyle == "slider" and
+ user_opts.seekbarstyle == "bar" then
+ msg.warn("Using \"slider\" seekrangestyle together with \"bar\" seekbarstyle is not supported")
+ user_opts.seekrangestyle = "inverted"
+ end
+
+ if user_opts.windowcontrols ~= "auto" and
+ user_opts.windowcontrols ~= "yes" and
+ user_opts.windowcontrols ~= "no" then
+ msg.warn("windowcontrols cannot be \"" ..
+ user_opts.windowcontrols .. "\". Ignoring.")
+ user_opts.windowcontrols = "auto"
+ end
+ if user_opts.windowcontrols_alignment ~= "right" and
+ user_opts.windowcontrols_alignment ~= "left" then
+ msg.warn("windowcontrols_alignment cannot be \"" ..
+ user_opts.windowcontrols_alignment .. "\". Ignoring.")
+ user_opts.windowcontrols_alignment = "right"
+ end
+end
+
+function update_options(list)
+ validate_user_opts()
+ request_tick()
+ visibility_mode(user_opts.visibility, true)
+ update_duration_watch()
+ request_init()
+end
+
+local UNICODE_MINUS = string.char(0xe2, 0x88, 0x92) -- UTF-8 for U+2212 MINUS SIGN
+
+-- OSC INIT
+function osc_init()
+ msg.debug("osc_init")
+
+ -- set canvas resolution according to display aspect and scaling setting
+ local baseResY = 720
+ local display_w, display_h, display_aspect = mp.get_osd_size()
+ local scale = 1
+
+ if (mp.get_property("video") == "no") then -- dummy/forced window
+ scale = user_opts.scaleforcedwindow
+ elseif state.fullscreen then
+ scale = user_opts.scalefullscreen
+ else
+ scale = user_opts.scalewindowed
+ end
+
+ if user_opts.vidscale then
+ osc_param.unscaled_y = baseResY
+ else
+ osc_param.unscaled_y = display_h
+ end
+ osc_param.playresy = osc_param.unscaled_y / scale
+ if (display_aspect > 0) then
+ osc_param.display_aspect = display_aspect
+ end
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+
+ -- stop seeking with the slider to prevent skipping files
+ state.active_element = nil
+
+ osc_param.video_margins = {l = 0, r = 0, t = 0, b = 0}
+
+ elements = {}
+
+ -- some often needed stuff
+ local pl_count = mp.get_property_number("playlist-count", 0)
+ local have_pl = (pl_count > 1)
+ local pl_pos = mp.get_property_number("playlist-pos", 0) + 1
+ local have_ch = (mp.get_property_number("chapters", 0) > 0)
+ local loop = mp.get_property("loop-playlist", "no")
+
+ local ne
+
+ -- title
+ ne = new_element("title", "button")
+
+ ne.content = function ()
+ local title = state.forced_title or
+ mp.command_native({"expand-text", user_opts.title})
+ -- escape ASS, and strip newlines and trailing slashes
+ title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{")
+ return not (title == "") and title or "mpv"
+ end
+
+ ne.eventresponder["mbtn_left_up"] = function ()
+ local title = mp.get_property_osd("media-title")
+ if (have_pl) then
+ title = string.format("[%d/%d] %s", countone(pl_pos - 1),
+ pl_count, title)
+ end
+ show_message(title)
+ end
+
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(mp.get_property_osd("filename")) end
+
+ -- playlist buttons
+
+ -- prev
+ ne = new_element("pl_prev", "button")
+
+ ne.content = "\238\132\144"
+ ne.enabled = (pl_pos > 1) or (loop ~= "no")
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("playlist-prev", "weak")
+ if user_opts.playlist_osd then
+ show_message(get_playlist(), 3)
+ end
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_playlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_playlist(), 3) end
+
+ --next
+ ne = new_element("pl_next", "button")
+
+ ne.content = "\238\132\129"
+ ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("playlist-next", "weak")
+ if user_opts.playlist_osd then
+ show_message(get_playlist(), 3)
+ end
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_playlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_playlist(), 3) end
+
+
+ -- big buttons
+
+ --playpause
+ ne = new_element("playpause", "button")
+
+ ne.content = function ()
+ if mp.get_property("pause") == "yes" then
+ return ("\238\132\129")
+ else
+ return ("\238\128\130")
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "pause") end
+
+ --skipback
+ ne = new_element("skipback", "button")
+
+ ne.softrepeat = true
+ ne.content = "\238\128\132"
+ ne.eventresponder["mbtn_left_down"] =
+ function () mp.commandv("seek", -5, "relative", "keyframes") end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () mp.commandv("frame-back-step") end
+ ne.eventresponder["mbtn_right_down"] =
+ function () mp.commandv("seek", -30, "relative", "keyframes") end
+
+ --skipfrwd
+ ne = new_element("skipfrwd", "button")
+
+ ne.softrepeat = true
+ ne.content = "\238\128\133"
+ ne.eventresponder["mbtn_left_down"] =
+ function () mp.commandv("seek", 10, "relative", "keyframes") end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () mp.commandv("frame-step") end
+ ne.eventresponder["mbtn_right_down"] =
+ function () mp.commandv("seek", 60, "relative", "keyframes") end
+
+ --ch_prev
+ ne = new_element("ch_prev", "button")
+
+ ne.enabled = have_ch
+ ne.content = "\238\132\132"
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("add", "chapter", -1)
+ if user_opts.chapters_osd then
+ show_message(get_chapterlist(), 3)
+ end
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_chapterlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_chapterlist(), 3) end
+
+ --ch_next
+ ne = new_element("ch_next", "button")
+
+ ne.enabled = have_ch
+ ne.content = "\238\132\133"
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("add", "chapter", 1)
+ if user_opts.chapters_osd then
+ show_message(get_chapterlist(), 3)
+ end
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_chapterlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_chapterlist(), 3) end
+
+ --
+ update_tracklist()
+
+ --cy_audio
+ ne = new_element("cy_audio", "button")
+
+ ne.enabled = (#tracks_osc.audio > 0)
+ ne.content = function ()
+ local aid = "–"
+ if not (get_track("audio") == 0) then
+ aid = get_track("audio")
+ end
+ return ("\238\132\134" .. osc_styles.smallButtonsLlabel
+ .. " " .. aid .. "/" .. #tracks_osc.audio)
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () set_track("audio", 1) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () set_track("audio", -1) end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () show_message(get_tracklist("audio"), 2) end
+
+ if user_opts.scrollcontrols then
+ ne.eventresponder["wheel_down_press"] =
+ function () set_track("audio", 1) end
+ ne.eventresponder["wheel_up_press"] =
+ function () set_track("audio", -1) end
+ end
+
+ --cy_sub
+ ne = new_element("cy_sub", "button")
+
+ ne.enabled = (#tracks_osc.sub > 0)
+ ne.content = function ()
+ local sid = "–"
+ if not (get_track("sub") == 0) then
+ sid = get_track("sub")
+ end
+ return ("\238\132\135" .. osc_styles.smallButtonsLlabel
+ .. " " .. sid .. "/" .. #tracks_osc.sub)
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () set_track("sub", 1) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () set_track("sub", -1) end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () show_message(get_tracklist("sub"), 2) end
+
+ if user_opts.scrollcontrols then
+ ne.eventresponder["wheel_down_press"] =
+ function () set_track("sub", 1) end
+ ne.eventresponder["wheel_up_press"] =
+ function () set_track("sub", -1) end
+ end
+
+ --tog_fs
+ ne = new_element("tog_fs", "button")
+ ne.content = function ()
+ if (state.fullscreen) then
+ return ("\238\132\137")
+ else
+ return ("\238\132\136")
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "fullscreen") end
+
+ --seekbar
+ ne = new_element("seekbar", "slider")
+
+ ne.enabled = not (mp.get_property("percent-pos") == nil)
+ state.slider_element = ne.enabled and ne or nil -- used for forced_title
+ ne.slider.markerF = function ()
+ local duration = mp.get_property_number("duration", nil)
+ if not (duration == nil) then
+ local chapters = mp.get_property_native("chapter-list", {})
+ local markers = {}
+ for n = 1, #chapters do
+ markers[n] = (chapters[n].time / duration * 100)
+ end
+ return markers
+ else
+ return {}
+ end
+ end
+ ne.slider.posF =
+ function () return mp.get_property_number("percent-pos", nil) end
+ ne.slider.tooltipF = function (pos)
+ local duration = mp.get_property_number("duration", nil)
+ if not ((duration == nil) or (pos == nil)) then
+ possec = duration * (pos / 100)
+ return mp.format_time(possec)
+ else
+ return ""
+ end
+ end
+ ne.slider.seekRangesF = function()
+ if user_opts.seekrangestyle == "none" then
+ return nil
+ end
+ local cache_state = state.cache_state
+ if not cache_state then
+ return nil
+ end
+ local duration = mp.get_property_number("duration", nil)
+ if (duration == nil) or duration <= 0 then
+ return nil
+ end
+ local ranges = cache_state["seekable-ranges"]
+ if #ranges == 0 then
+ return nil
+ end
+ local nranges = {}
+ for _, range in pairs(ranges) do
+ nranges[#nranges + 1] = {
+ ["start"] = 100 * range["start"] / duration,
+ ["end"] = 100 * range["end"] / duration,
+ }
+ end
+ return nranges
+ end
+ ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged
+ function (element)
+ -- mouse move events may pile up during seeking and may still get
+ -- sent when the user is done seeking, so we need to throw away
+ -- identical seeks
+ local seekto = get_slider_value(element)
+ if (element.state.lastseek == nil) or
+ (not (element.state.lastseek == seekto)) then
+ local flags = "absolute-percent"
+ if not user_opts.seekbarkeyframes then
+ flags = flags .. "+exact"
+ end
+ mp.commandv("seek", seekto, flags)
+ element.state.lastseek = seekto
+ end
+
+ end
+ ne.eventresponder["mbtn_left_down"] = --exact seeks on single clicks
+ function (element) mp.commandv("seek", get_slider_value(element),
+ "absolute-percent", "exact") end
+ ne.eventresponder["reset"] =
+ function (element) element.state.lastseek = nil end
+
+ if user_opts.scrollcontrols then
+ ne.eventresponder["wheel_up_press"] =
+ function () mp.commandv("osd-auto", "seek", 10) end
+ ne.eventresponder["wheel_down_press"] =
+ function () mp.commandv("osd-auto", "seek", -10) end
+ end
+
+
+ -- tc_left (current pos)
+ ne = new_element("tc_left", "button")
+
+ ne.content = function ()
+ if (state.tc_ms) then
+ return (mp.get_property_osd("playback-time/full"))
+ else
+ return (mp.get_property_osd("playback-time"))
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] = function ()
+ state.tc_ms = not state.tc_ms
+ request_init()
+ end
+
+ -- tc_right (total/remaining time)
+ ne = new_element("tc_right", "button")
+
+ ne.visible = (mp.get_property_number("duration", 0) > 0)
+ ne.content = function ()
+ if (state.rightTC_trem) then
+ local minus = user_opts.unicodeminus and UNICODE_MINUS or "-"
+ local property = user_opts.remaining_playtime and "playtime-remaining"
+ or "time-remaining"
+ if state.tc_ms then
+ return (minus..mp.get_property_osd(property .. "/full"))
+ else
+ return (minus..mp.get_property_osd(property))
+ end
+ else
+ if state.tc_ms then
+ return (mp.get_property_osd("duration/full"))
+ else
+ return (mp.get_property_osd("duration"))
+ end
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () state.rightTC_trem = not state.rightTC_trem end
+
+ -- cache
+ ne = new_element("cache", "button")
+
+ ne.content = function ()
+ local cache_state = state.cache_state
+ if not (cache_state and cache_state["seekable-ranges"] and
+ #cache_state["seekable-ranges"] > 0) then
+ -- probably not a network stream
+ return ""
+ end
+ local dmx_cache = cache_state and cache_state["cache-duration"]
+ local thresh = math.min(state.dmx_cache * 0.05, 5) -- 5% or 5s
+ if dmx_cache and math.abs(dmx_cache - state.dmx_cache) >= thresh then
+ state.dmx_cache = dmx_cache
+ else
+ dmx_cache = state.dmx_cache
+ end
+ local min = math.floor(dmx_cache / 60)
+ local sec = math.floor(dmx_cache % 60) -- don't round e.g. 59.9 to 60
+ return "Cache: " .. (min > 0 and
+ string.format("%sm%02.0fs", min, sec) or
+ string.format("%3.0fs", sec))
+ end
+
+ -- volume
+ ne = new_element("volume", "button")
+
+ ne.content = function()
+ local volume = mp.get_property_number("volume", 0)
+ local mute = mp.get_property_native("mute")
+ local volicon = {"\238\132\139", "\238\132\140",
+ "\238\132\141", "\238\132\142"}
+ if volume == 0 or mute then
+ return "\238\132\138"
+ else
+ return volicon[math.min(4,math.ceil(volume / (100/3)))]
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "mute") end
+
+ if user_opts.scrollcontrols then
+ ne.eventresponder["wheel_up_press"] =
+ function () mp.commandv("osd-auto", "add", "volume", 5) end
+ ne.eventresponder["wheel_down_press"] =
+ function () mp.commandv("osd-auto", "add", "volume", -5) end
+ end
+
+
+ -- load layout
+ layouts[user_opts.layout]()
+
+ -- load window controls
+ if window_controls_enabled() then
+ window_controls(user_opts.layout == "topbar")
+ end
+
+ --do something with the elements
+ prepare_elements()
+
+ update_margins()
+end
+
+function reset_margins()
+ if state.using_video_margins then
+ for _, opt in ipairs(margins_opts) do
+ mp.set_property_number(opt[2], 0.0)
+ end
+ state.using_video_margins = false
+ end
+end
+
+function update_margins()
+ local margins = osc_param.video_margins
+
+ -- Don't use margins if it's visible only temporarily.
+ if (not state.osc_visible) or (get_hidetimeout() >= 0) or
+ (state.fullscreen and not user_opts.showfullscreen) or
+ (not state.fullscreen and not user_opts.showwindowed)
+ then
+ margins = {l = 0, r = 0, t = 0, b = 0}
+ end
+
+ if user_opts.boxvideo then
+ -- check whether any margin option has a non-default value
+ local margins_used = false
+
+ if not state.using_video_margins then
+ for _, opt in ipairs(margins_opts) do
+ if mp.get_property_number(opt[2], 0.0) ~= 0.0 then
+ margins_used = true
+ end
+ end
+ end
+
+ if not margins_used then
+ for _, opt in ipairs(margins_opts) do
+ local v = margins[opt[1]]
+ if (v ~= 0) or state.using_video_margins then
+ mp.set_property_number(opt[2], v)
+ state.using_video_margins = true
+ end
+ end
+ end
+ else
+ reset_margins()
+ end
+
+ if mp.del_property then
+ mp.set_property_native("user-data/osc/margins", margins)
+ else
+ utils.shared_script_property_set("osc-margins",
+ string.format("%f,%f,%f,%f", margins.l, margins.r, margins.t, margins.b))
+ end
+end
+
+function shutdown()
+ reset_margins()
+ if mp.del_property then
+ mp.del_property("user-data/osc")
+ else
+ utils.shared_script_property_set("osc-margins", nil)
+ end
+end
+
+--
+-- Other important stuff
+--
+
+
+function show_osc()
+ -- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding
+ if not state.enabled then return end
+
+ msg.trace("show_osc")
+ --remember last time of invocation (mouse move)
+ state.showtime = mp.get_time()
+
+ osc_visible(true)
+
+ if (user_opts.fadeduration > 0) then
+ state.anitype = nil
+ end
+end
+
+function hide_osc()
+ msg.trace("hide_osc")
+ if thumbfast.width ~= 0 and thumbfast.height ~= 0 then
+ mp.commandv("script-message-to", "thumbfast", "clear")
+ end
+ if not state.enabled then
+ -- typically hide happens at render() from tick(), but now tick() is
+ -- no-op and won't render again to remove the osc, so do that manually.
+ state.osc_visible = false
+ render_wipe()
+ elseif (user_opts.fadeduration > 0) then
+ if not(state.osc_visible == false) then
+ state.anitype = "out"
+ request_tick()
+ end
+ else
+ osc_visible(false)
+ end
+end
+
+function osc_visible(visible)
+ if state.osc_visible ~= visible then
+ state.osc_visible = visible
+ update_margins()
+ end
+ request_tick()
+end
+
+function pause_state(name, enabled)
+ state.paused = enabled
+ request_tick()
+end
+
+function cache_state(name, st)
+ state.cache_state = st
+ request_tick()
+end
+
+-- Request that tick() is called (which typically re-renders the OSC).
+-- The tick is then either executed immediately, or rate-limited if it was
+-- called a small time ago.
+function request_tick()
+ if state.tick_timer == nil then
+ state.tick_timer = mp.add_timeout(0, tick)
+ end
+
+ if not state.tick_timer:is_enabled() then
+ local now = mp.get_time()
+ local timeout = tick_delay - (now - state.tick_last_time)
+ if timeout < 0 then
+ timeout = 0
+ end
+ state.tick_timer.timeout = timeout
+ state.tick_timer:resume()
+ end
+end
+
+function mouse_leave()
+ if get_hidetimeout() >= 0 then
+ hide_osc()
+ end
+ -- reset mouse position
+ state.last_mouseX, state.last_mouseY = nil, nil
+ state.mouse_in_window = false
+end
+
+function request_init()
+ state.initREQ = true
+ request_tick()
+end
+
+-- Like request_init(), but also request an immediate update
+function request_init_resize()
+ request_init()
+ -- ensure immediate update
+ state.tick_timer:kill()
+ state.tick_timer.timeout = 0
+ state.tick_timer:resume()
+end
+
+function render_wipe()
+ msg.trace("render_wipe()")
+ if state.osd then
+ state.osd.data = "" -- allows set_osd to immediately update on enable
+ state.osd:remove()
+ else
+ set_osd(0, 0, "{}")
+ end
+end
+
+function render()
+ msg.trace("rendering")
+ local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size()
+ local mouseX, mouseY = get_virt_mouse_pos()
+ local now = mp.get_time()
+
+ -- check if display changed, if so request reinit
+ if not (state.mp_screen_sizeX == current_screen_sizeX
+ and state.mp_screen_sizeY == current_screen_sizeY) then
+
+ request_init_resize()
+
+ state.mp_screen_sizeX = current_screen_sizeX
+ state.mp_screen_sizeY = current_screen_sizeY
+ end
+
+ -- init management
+ if state.active_element then
+ -- mouse is held down on some element - keep ticking and ignore initReq
+ -- till it's released, or else the mouse-up (click) will misbehave or
+ -- get ignored. that's because osc_init() recreates the osc elements,
+ -- but mouse handling depends on the elements staying unmodified
+ -- between mouse-down and mouse-up (using the index active_element).
+ request_tick()
+ elseif state.initREQ then
+ osc_init()
+ state.initREQ = false
+
+ -- store initial mouse position
+ if (state.last_mouseX == nil or state.last_mouseY == nil)
+ and not (mouseX == nil or mouseY == nil) then
+
+ state.last_mouseX, state.last_mouseY = mouseX, mouseY
+ end
+ end
+
+
+ -- fade animation
+ if not(state.anitype == nil) then
+
+ if (state.anistart == nil) then
+ state.anistart = now
+ end
+
+ if (now < state.anistart + (user_opts.fadeduration/1000)) then
+
+ if (state.anitype == "in") then --fade in
+ osc_visible(true)
+ state.animation = scale_value(state.anistart,
+ (state.anistart + (user_opts.fadeduration/1000)),
+ 255, 0, now)
+ elseif (state.anitype == "out") then --fade out
+ state.animation = scale_value(state.anistart,
+ (state.anistart + (user_opts.fadeduration/1000)),
+ 0, 255, now)
+ end
+
+ else
+ if (state.anitype == "out") then
+ osc_visible(false)
+ end
+ kill_animation()
+ end
+ else
+ kill_animation()
+ end
+
+ --mouse show/hide area
+ for k,cords in pairs(osc_param.areas["showhide"]) do
+ set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-showhide")
+ end
+ if osc_param.areas["showhide_wc"] then
+ for k,cords in pairs(osc_param.areas["showhide_wc"]) do
+ set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-showhide_wc")
+ end
+ else
+ set_virt_mouse_area(0, 0, 0, 0, "thumbfast-osc-showhide_wc")
+ end
+ do_enable_keybindings()
+
+ --mouse input area
+ local mouse_over_osc = false
+
+ for _,cords in ipairs(osc_param.areas["input"]) do
+ if state.osc_visible then -- activate only when OSC is actually visible
+ set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-input")
+ end
+ if state.osc_visible ~= state.input_enabled then
+ if state.osc_visible then
+ mp.enable_key_bindings("thumbfast-osc-input")
+ else
+ mp.disable_key_bindings("thumbfast-osc-input")
+ end
+ state.input_enabled = state.osc_visible
+ end
+
+ if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
+ mouse_over_osc = true
+ end
+ end
+
+ if osc_param.areas["window-controls"] then
+ for _,cords in ipairs(osc_param.areas["window-controls"]) do
+ if state.osc_visible then -- activate only when OSC is actually visible
+ set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-window-controls")
+ end
+ if state.osc_visible ~= state.windowcontrols_buttons then
+ if state.osc_visible then
+ mp.enable_key_bindings("thumbfast-osc-window-controls")
+ else
+ mp.disable_key_bindings("thumbfast-osc-window-controls")
+ end
+ state.windowcontrols_buttons = state.osc_visible
+ end
+
+ if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
+ mouse_over_osc = true
+ end
+ end
+ end
+
+ if osc_param.areas["window-controls-title"] then
+ for _,cords in ipairs(osc_param.areas["window-controls-title"]) do
+ if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
+ mouse_over_osc = true
+ end
+ end
+ end
+
+ -- autohide
+ if not (state.showtime == nil) and (get_hidetimeout() >= 0) then
+ local timeout = state.showtime + (get_hidetimeout()/1000) - now
+ if timeout <= 0 then
+ if (state.active_element == nil) and not (mouse_over_osc) then
+ hide_osc()
+ end
+ else
+ -- the timer is only used to recheck the state and to possibly run
+ -- the code above again
+ if not state.hide_timer then
+ state.hide_timer = mp.add_timeout(0, tick)
+ end
+ state.hide_timer.timeout = timeout
+ -- re-arm
+ state.hide_timer:kill()
+ state.hide_timer:resume()
+ end
+ end
+
+
+ -- actual rendering
+ local ass = assdraw.ass_new()
+
+ -- Messages
+ render_message(ass)
+
+ -- actual OSC
+ if state.osc_visible then
+ render_elements(ass)
+ end
+
+ -- submit
+ set_osd(osc_param.playresy * osc_param.display_aspect,
+ osc_param.playresy, ass.text, 1000)
+end
+
+--
+-- Eventhandling
+--
+
+local function element_has_action(element, action)
+ return element and element.eventresponder and
+ element.eventresponder[action]
+end
+
+function process_event(source, what)
+ local action = string.format("%s%s", source,
+ what and ("_" .. what) or "")
+
+ if what == "down" or what == "press" then
+
+ for n = 1, #elements do
+
+ if mouse_hit(elements[n]) and
+ elements[n].eventresponder and
+ (elements[n].eventresponder[source .. "_up"] or
+ elements[n].eventresponder[action]) then
+
+ if what == "down" then
+ state.active_element = n
+ state.active_event_source = source
+ end
+ -- fire the down or press event if the element has one
+ if element_has_action(elements[n], action) then
+ elements[n].eventresponder[action](elements[n])
+ end
+
+ end
+ end
+
+ elseif what == "up" then
+
+ if elements[state.active_element] then
+ local n = state.active_element
+
+ if n == 0 then
+ --click on background (does not work)
+ elseif element_has_action(elements[n], action) and
+ mouse_hit(elements[n]) then
+
+ elements[n].eventresponder[action](elements[n])
+ end
+
+ --reset active element
+ if element_has_action(elements[n], "reset") then
+ elements[n].eventresponder["reset"](elements[n])
+ end
+
+ end
+ state.active_element = nil
+ state.mouse_down_counter = 0
+
+ elseif source == "mouse_move" then
+
+ state.mouse_in_window = true
+
+ local mouseX, mouseY = get_virt_mouse_pos()
+ if (user_opts.minmousemove == 0) or
+ (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and
+ ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove)
+ or (math.abs(mouseY - state.last_mouseY) >= user_opts.minmousemove)
+ )
+ ) then
+ show_osc()
+ end
+ state.last_mouseX, state.last_mouseY = mouseX, mouseY
+
+ local n = state.active_element
+ if element_has_action(elements[n], action) then
+ elements[n].eventresponder[action](elements[n])
+ end
+ end
+
+ -- ensure rendering after any (mouse) event - icons could change etc
+ request_tick()
+end
+
+
+local logo_lines = {
+ -- White border
+ "{\\c&HE5E5E5&\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 {\\p0}",
+ -- Purple fill
+ "{\\c&H682167&\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42{\\p0}",
+ -- Darker fill
+ "{\\c&H430142&\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}",
+ -- White fill
+ "{\\c&HDDDBDD&\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}",
+ -- Triangle
+ "{\\c&H691F69&\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}",
+}
+
+local santa_hat_lines = {
+ -- Pompoms
+ "{\\c&HC0C0C0&\\p6}m 500 -323 b 491 -322 481 -318 475 -311 465 -312 456 -319 446 -318 434 -314 427 -304 417 -297 410 -290 404 -282 395 -278 390 -274 387 -267 381 -265 377 -261 379 -254 384 -253 397 -244 409 -232 425 -228 437 -228 446 -218 457 -217 462 -216 466 -213 468 -209 471 -205 477 -203 482 -206 491 -211 499 -217 508 -222 532 -235 556 -249 576 -267 584 -272 584 -284 578 -290 569 -305 550 -312 533 -309 523 -310 515 -316 507 -321 505 -323 503 -323 500 -323{\\p0}",
+ "{\\c&HE0E0E0&\\p6}m 315 -260 b 286 -258 259 -240 246 -215 235 -210 222 -215 211 -211 204 -188 177 -176 172 -151 170 -139 163 -128 154 -121 143 -103 141 -81 143 -60 139 -46 125 -34 129 -17 132 -1 134 16 142 30 145 56 161 80 181 96 196 114 210 133 231 144 266 153 303 138 328 115 373 79 401 28 423 -24 446 -73 465 -123 483 -174 487 -199 467 -225 442 -227 421 -232 402 -242 384 -254 364 -259 342 -250 322 -260 320 -260 317 -261 315 -260{\\p0}",
+ -- Main cap
+ "{\\c&H0000F0&\\p6}m 1151 -523 b 1016 -516 891 -458 769 -406 693 -369 624 -319 561 -262 526 -252 465 -235 479 -187 502 -147 551 -135 588 -111 1115 165 1379 232 1909 761 1926 800 1952 834 1987 858 2020 883 2053 912 2065 952 2088 1000 2146 962 2139 919 2162 836 2156 747 2143 662 2131 615 2116 567 2122 517 2120 410 2090 306 2089 199 2092 147 2071 99 2034 64 1987 5 1928 -41 1869 -86 1777 -157 1712 -256 1629 -337 1578 -389 1521 -436 1461 -476 1407 -509 1343 -507 1284 -515 1240 -519 1195 -521 1151 -523{\\p0}",
+ -- Cap shadow
+ "{\\c&H0000AA&\\p6}m 1657 248 b 1658 254 1659 261 1660 267 1669 276 1680 284 1689 293 1695 302 1700 311 1707 320 1716 325 1726 330 1735 335 1744 347 1752 360 1761 371 1753 352 1754 331 1753 311 1751 237 1751 163 1751 90 1752 64 1752 37 1767 14 1778 -3 1785 -24 1786 -45 1786 -60 1786 -77 1774 -87 1760 -96 1750 -78 1751 -65 1748 -37 1750 -8 1750 20 1734 78 1715 134 1699 192 1694 211 1689 231 1676 246 1671 251 1661 255 1657 248 m 1909 541 b 1914 542 1922 549 1917 539 1919 520 1921 502 1919 483 1918 458 1917 433 1915 407 1930 373 1942 338 1947 301 1952 270 1954 238 1951 207 1946 214 1947 229 1945 239 1939 278 1936 318 1924 356 1923 362 1913 382 1912 364 1906 301 1904 237 1891 175 1887 150 1892 126 1892 101 1892 68 1893 35 1888 2 1884 -9 1871 -20 1859 -14 1851 -6 1854 9 1854 20 1855 58 1864 95 1873 132 1883 179 1894 225 1899 273 1908 362 1910 451 1909 541{\\p0}",
+ -- Brim and tip pompom
+ "{\\c&HF8F8F8&\\p6}m 626 -191 b 565 -155 486 -196 428 -151 387 -115 327 -101 304 -47 273 2 267 59 249 113 219 157 217 213 215 265 217 309 260 302 285 283 373 264 465 264 555 257 608 252 655 292 709 287 759 294 816 276 863 298 903 340 972 324 1012 367 1061 394 1125 382 1167 424 1213 462 1268 482 1322 506 1385 546 1427 610 1479 662 1510 690 1534 725 1566 752 1611 796 1664 830 1703 880 1740 918 1747 986 1805 1005 1863 991 1897 932 1916 880 1914 823 1945 777 1961 725 1979 673 1957 622 1938 575 1912 534 1862 515 1836 473 1790 417 1755 351 1697 305 1658 266 1633 216 1593 176 1574 138 1539 116 1497 110 1448 101 1402 77 1371 37 1346 -16 1295 15 1254 6 1211 -27 1170 -62 1121 -86 1072 -104 1027 -128 976 -133 914 -130 851 -137 794 -162 740 -181 679 -168 626 -191 m 2051 917 b 1971 932 1929 1017 1919 1091 1912 1149 1923 1214 1970 1254 2000 1279 2027 1314 2066 1325 2139 1338 2212 1295 2254 1238 2281 1203 2287 1158 2282 1116 2292 1061 2273 1006 2229 970 2206 941 2167 938 2138 918{\\p0}",
+}
+
+-- called by mpv on every frame
+function tick()
+ if state.marginsREQ == true then
+ update_margins()
+ state.marginsREQ = false
+ end
+
+ if (not state.enabled) then return end
+
+ if (state.idle) then
+
+ -- render idle message
+ msg.trace("idle message")
+ local _, _, display_aspect = mp.get_osd_size()
+ if display_aspect == 0 then
+ return
+ end
+ local display_h = 360
+ local display_w = display_h * display_aspect
+ -- logo is rendered at 2^(6-1) = 32 times resolution with size 1800x1800
+ local icon_x, icon_y = (display_w - 1800 / 32) / 2, 140
+ local line_prefix = ("{\\rDefault\\an7\\1a&H00&\\bord0\\shad0\\pos(%f,%f)}"):format(icon_x, icon_y)
+
+ local ass = assdraw.ass_new()
+ -- mpv logo
+ if user_opts.idlescreen then
+ for i, line in ipairs(logo_lines) do
+ ass:new_event()
+ ass:append(line_prefix .. line)
+ end
+ end
+
+ -- Santa hat
+ if is_december and user_opts.idlescreen and not user_opts.greenandgrumpy then
+ for i, line in ipairs(santa_hat_lines) do
+ ass:new_event()
+ ass:append(line_prefix .. line)
+ end
+ end
+
+ if user_opts.idlescreen then
+ ass:new_event()
+ ass:pos(display_w / 2, icon_y + 65)
+ ass:an(8)
+ ass:append("Drop files or URLs to play here.")
+ end
+ set_osd(display_w, display_h, ass.text, -1000)
+
+ if state.showhide_enabled then
+ mp.disable_key_bindings("thumbfast-osc-showhide")
+ mp.disable_key_bindings("thumbfast-osc-showhide_wc")
+ state.showhide_enabled = false
+ end
+
+
+ elseif (state.fullscreen and user_opts.showfullscreen)
+ or (not state.fullscreen and user_opts.showwindowed) then
+
+ -- render the OSC
+ render()
+ else
+ -- Flush OSD
+ render_wipe()
+ end
+
+ state.tick_last_time = mp.get_time()
+
+ if state.anitype ~= nil then
+ -- state.anistart can be nil - animation should now start, or it can
+ -- be a timestamp when it started. state.idle has no animation.
+ if not state.idle and
+ (not state.anistart or
+ mp.get_time() < 1 + state.anistart + user_opts.fadeduration/1000)
+ then
+ -- animating or starting, or still within 1s past the deadline
+ request_tick()
+ else
+ kill_animation()
+ end
+ end
+end
+
+function do_enable_keybindings()
+ if state.enabled then
+ if not state.showhide_enabled then
+ mp.enable_key_bindings("thumbfast-osc-showhide", "allow-vo-dragging+allow-hide-cursor")
+ mp.enable_key_bindings("thumbfast-osc-showhide_wc", "allow-vo-dragging+allow-hide-cursor")
+ end
+ state.showhide_enabled = true
+ end
+end
+
+function enable_osc(enable)
+ state.enabled = enable
+ if enable then
+ do_enable_keybindings()
+ else
+ hide_osc() -- acts immediately when state.enabled == false
+ if state.showhide_enabled then
+ mp.disable_key_bindings("thumbfast-osc-showhide")
+ mp.disable_key_bindings("thumbfast-osc-showhide_wc")
+ end
+ state.showhide_enabled = false
+ end
+end
+
+-- duration is observed for the sole purpose of updating chapter markers
+-- positions. live streams with chapters are very rare, and the update is also
+-- expensive (with request_init), so it's only observed when we have chapters
+-- and the user didn't disable the livemarkers option (update_duration_watch).
+function on_duration() request_init() end
+
+local duration_watched = false
+function update_duration_watch()
+ local want_watch = user_opts.livemarkers and
+ (mp.get_property_number("chapters", 0) or 0) > 0 and
+ true or false -- ensure it's a boolean
+
+ if (want_watch ~= duration_watched) then
+ if want_watch then
+ mp.observe_property("duration", nil, on_duration)
+ else
+ mp.unobserve_property(on_duration)
+ end
+ duration_watched = want_watch
+ end
+end
+
+validate_user_opts()
+update_duration_watch()
+
+mp.register_event("shutdown", shutdown)
+mp.register_event("start-file", request_init)
+mp.observe_property("track-list", nil, request_init)
+mp.observe_property("playlist", nil, request_init)
+mp.observe_property("chapter-list", "native", function(_, list)
+ list = list or {} -- safety, shouldn't return nil
+ table.sort(list, function(a, b) return a.time < b.time end)
+ state.chapter_list = list
+ update_duration_watch()
+ request_init()
+end)
+
+mp.register_script_message("osc-message", show_message)
+mp.register_script_message("osc-chapterlist", function(dur)
+ show_message(get_chapterlist(), dur)
+end)
+mp.register_script_message("osc-playlist", function(dur)
+ show_message(get_playlist(), dur)
+end)
+mp.register_script_message("osc-tracklist", function(dur)
+ local msg = {}
+ for k,v in pairs(nicetypes) do
+ table.insert(msg, get_tracklist(k))
+ end
+ show_message(table.concat(msg, '\n\n'), dur)
+end)
+
+mp.observe_property("fullscreen", "bool",
+ function(name, val)
+ state.fullscreen = val
+ state.marginsREQ = true
+ request_init_resize()
+ end
+)
+mp.observe_property("border", "bool",
+ function(name, val)
+ state.border = val
+ request_init_resize()
+ end
+)
+mp.observe_property("window-maximized", "bool",
+ function(name, val)
+ state.maximized = val
+ request_init_resize()
+ end
+)
+mp.observe_property("idle-active", "bool",
+ function(name, val)
+ state.idle = val
+ request_tick()
+ end
+)
+mp.observe_property("pause", "bool", pause_state)
+mp.observe_property("demuxer-cache-state", "native", cache_state)
+mp.observe_property("vo-configured", "bool", function(name, val)
+ request_tick()
+end)
+mp.observe_property("playback-time", "number", function(name, val)
+ request_tick()
+end)
+mp.observe_property("osd-dimensions", "native", function(name, val)
+ -- (we could use the value instead of re-querying it all the time, but then
+ -- we might have to worry about property update ordering)
+ request_init_resize()
+end)
+
+-- mouse show/hide bindings
+mp.set_key_bindings({
+ {"mouse_move", function(e) process_event("mouse_move", nil) end},
+ {"mouse_leave", mouse_leave},
+}, "thumbfast-osc-showhide", "force")
+mp.set_key_bindings({
+ {"mouse_move", function(e) process_event("mouse_move", nil) end},
+ {"mouse_leave", mouse_leave},
+}, "thumbfast-osc-showhide_wc", "force")
+do_enable_keybindings()
+
+--mouse input bindings
+mp.set_key_bindings({
+ {"mbtn_left", function(e) process_event("mbtn_left", "up") end,
+ function(e) process_event("mbtn_left", "down") end},
+ {"shift+mbtn_left", function(e) process_event("shift+mbtn_left", "up") end,
+ function(e) process_event("shift+mbtn_left", "down") end},
+ {"mbtn_right", function(e) process_event("mbtn_right", "up") end,
+ function(e) process_event("mbtn_right", "down") end},
+ -- alias to shift_mbtn_left for single-handed mouse use
+ {"mbtn_mid", function(e) process_event("shift+mbtn_left", "up") end,
+ function(e) process_event("shift+mbtn_left", "down") end},
+ {"wheel_up", function(e) process_event("wheel_up", "press") end},
+ {"wheel_down", function(e) process_event("wheel_down", "press") end},
+ {"mbtn_left_dbl", "ignore"},
+ {"shift+mbtn_left_dbl", "ignore"},
+ {"mbtn_right_dbl", "ignore"},
+}, "thumbfast-osc-input", "force")
+mp.enable_key_bindings("thumbfast-osc-input")
+
+mp.set_key_bindings({
+ {"mbtn_left", function(e) process_event("mbtn_left", "up") end,
+ function(e) process_event("mbtn_left", "down") end},
+}, "thumbfast-osc-window-controls", "force")
+mp.enable_key_bindings("thumbfast-osc-window-controls")
+
+function get_hidetimeout()
+ if user_opts.visibility == "always" then
+ return -1 -- disable autohide
+ end
+ return user_opts.hidetimeout
+end
+
+function always_on(val)
+ if state.enabled then
+ if val then
+ show_osc()
+ else
+ hide_osc()
+ end
+ end
+end
+
+-- mode can be auto/always/never/cycle
+-- the modes only affect internal variables and not stored on its own.
+function visibility_mode(mode, no_osd)
+ if mode == "cycle" then
+ if not state.enabled then
+ mode = "auto"
+ elseif user_opts.visibility ~= "always" then
+ mode = "always"
+ else
+ mode = "never"
+ end
+ end
+
+ if mode == "auto" then
+ always_on(false)
+ enable_osc(true)
+ elseif mode == "always" then
+ enable_osc(true)
+ always_on(true)
+ elseif mode == "never" then
+ enable_osc(false)
+ else
+ msg.warn("Ignoring unknown visibility mode '" .. mode .. "'")
+ return
+ end
+
+ user_opts.visibility = mode
+ if mp.del_property then
+ mp.set_property_native("user-data/osc/visibility", mode)
+ else
+ utils.shared_script_property_set("osc-visibility", mode)
+ end
+
+ if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
+ mp.osd_message("OSC visibility: " .. mode)
+ end
+
+ -- Reset the input state on a mode change. The input state will be
+ -- recalculated on the next render cycle, except in 'never' mode where it
+ -- will just stay disabled.
+ mp.disable_key_bindings("thumbfast-osc-input")
+ mp.disable_key_bindings("thumbfast-osc-window-controls")
+ state.input_enabled = false
+
+ update_margins()
+ request_tick()
+end
+
+function idlescreen_visibility(mode, no_osd)
+ if mode == "cycle" then
+ if user_opts.idlescreen then
+ mode = "no"
+ else
+ mode = "yes"
+ end
+ end
+
+ if mode == "yes" then
+ user_opts.idlescreen = true
+ else
+ user_opts.idlescreen = false
+ end
+
+ if mp.del_property then
+ mp.set_property_native("user-data/osc/idlescreen", user_opts.idlescreen)
+ else
+ utils.shared_script_property_set("osc-idlescreen", mode)
+ end
+
+ if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
+ mp.osd_message("OSC logo visibility: " .. tostring(mode))
+ end
+
+ request_tick()
+end
+
+visibility_mode(user_opts.visibility, true)
+mp.register_script_message("osc-visibility", visibility_mode)
+mp.add_key_binding(nil, "visibility", function() visibility_mode("cycle") end)
+
+mp.register_script_message("osc-idlescreen", idlescreen_visibility)
+
+mp.register_script_message("thumbfast-info", function(json)
+ local data = utils.parse_json(json)
+ if type(data) ~= "table" or not data.width or not data.height then
+ msg.error("thumbfast-info: received json didn't produce a table with thumbnail information")
+ else
+ thumbfast = data
+ end
+end)
+
+set_virt_mouse_area(0, 0, 0, 0, "thumbfast-osc-input")
+set_virt_mouse_area(0, 0, 0, 0, "thumbfast-osc-window-controls")
diff --git a/ar/.config/mpv/scripts/playlist-view.lua b/ar/.config/mpv/scripts/playlist-view.lua
new file mode 100644
index 0000000..3000e89
--- /dev/null
+++ b/ar/.config/mpv/scripts/playlist-view.lua
@@ -0,0 +1,925 @@
+--[[
+mpv-gallery-view | https://github.com/occivink/mpv-gallery-view
+
+This mpv script generates and displays an overview of the current playlist with thumbnails.
+
+File placement: scripts/playlist-view.lua
+Settings: script-opts/playlist_view.conf
+Requires: script-modules/gallery-module.lua
+Default keybinding: g script-binding playlist-view-toggle
+]]
+
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+local options = require("mp.options")
+
+package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
+require("gallery")
+
+ON_WINDOWS = (package.config:sub(1, 1) ~= "/")
+
+-- global variables
+
+flags = {}
+resume = {}
+did_pause = false
+hash_cache = {}
+playlist_pos = 0
+
+bindings = {}
+bindings_repeat = {}
+
+compute_geometry = function(ww, wh) end
+
+ass_changed = false
+ass = ""
+geometry_changed = false
+pending_selection = nil
+
+thumb_dir = ""
+
+gallery = gallery_new()
+gallery.config.always_show_placeholders = true
+gallery.config.accurate = false
+
+opts = {
+ thumbs_dir = ON_WINDOWS and "%APPDATA%\\mpv\\gallery-thumbs-dir" or "~/.cache/thumbnails/mpv-gallery/",
+ generate_thumbnails_with_mpv = ON_WINDOWS,
+ mkdir_thumbs = true,
+
+ gallery_position = "{ (ww - gw) / 2, (wh - gh) / 2}",
+ gallery_size = "{ 9 * ww / 10, 9 * wh / 10 }",
+ min_spacing = "{ 15, 15 }",
+ thumbnail_size = "(ww * wh <= 1366 * 768) and {192, 108} or {288, 162}",
+ max_thumbnails = 64,
+
+ take_thumbnail_at = "20%",
+
+ load_file_on_toggle_off = false,
+ close_on_load_file = true,
+ pause_on_start = true,
+ resume_on_stop = "only-if-did-pause",
+ follow_playlist_position = false,
+ remember_time_position = true,
+
+ start_on_mpv_startup = false,
+ start_on_file_end = true,
+
+ show_text = true,
+ show_title = true,
+ strip_directory = true,
+ strip_extension = true,
+ text_size = 28,
+
+ background_color = "333333",
+ background_opacity = "33",
+ normal_border_color = "BBBBBB",
+ normal_border_size = 1,
+ selected_border_color = "E5E4E5",
+ selected_border_size = 6,
+ highlight_active = true,
+ active_border_color = "EBC5A7",
+ active_border_size = 4,
+ flagged_border_color = "96B58D",
+ flagged_border_size = 4,
+ placeholder_color = "222222",
+
+ command_on_open = "",
+ command_on_close = "",
+
+ flagged_file_path = "./mpv/mpv_gallery_flagged",
+
+ mouse_support = true,
+ UP = "k",
+ DOWN = "j",
+ LEFT = "h",
+ RIGHT = "l",
+ PAGE_UP = "ctrl+u",
+ PAGE_DOWN = "ctrl+d",
+ FIRST = "0",
+ LAST = "$",
+ RANDOM = "r",
+ ACCEPT = "ENTER",
+ CANCEL = "ESC",
+ REMOVE = "BS",
+ FLAG = "SPACE",
+}
+function reload_config()
+ gallery.config.background_color = opts.background_color
+ gallery.config.background_opacity = opts.background_opacity
+ gallery.config.max_thumbnails = math.min(opts.max_thumbnails, 64)
+ gallery.config.placeholder_color = opts.placeholder_color
+ gallery.config.text_size = opts.text_size
+ gallery.config.generate_thumbnails_with_mpv = opts.generate_thumbnails_with_mpv
+ if ON_WINDOWS then
+ thumbs_dir = string.gsub(opts.thumbs_dir, "^%%APPDATA%%", os.getenv("APPDATA") or "%APPDATA%")
+ else
+ thumbs_dir = string.gsub(opts.thumbs_dir, "^~", os.getenv("HOME") or "~")
+ end
+ local res = utils.file_info(thumbs_dir)
+ if not res or not res.is_dir then
+ if opts.mkdir_thumbs then
+ local args = ON_WINDOWS and { "mkdir", thumbs_dir } or { "mkdir", "-p", thumbs_dir }
+ utils.subprocess({ args = args, playback_only = false })
+ else
+ msg.error(string.format('Thumbnail directory "%s" does not exist', thumbs_dir))
+ end
+ end
+
+ compute_geometry = get_geometry_function()
+ reload_bindings()
+ if gallery.active then
+ local ww, wh = mp.get_osd_size()
+ compute_geometry(ww, wh)
+ gallery:ass_refresh(true, true, true, true)
+ end
+end
+options.read_options(opts, mp.get_script_name(), reload_config)
+
+local sha256
+--[[
+minified code below is a combination of:
+-sha256 implementation from
+http://lua-users.org/wiki/SecureHashAlgorithm
+-lua implementation of bit32 (used as fallback on lua5.1) from
+https://www.snpedia.com/extensions/Scribunto/engines/LuaCommon/lualib/bit32.lua
+both are licensed under the MIT below:
+
+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.
+--]]
+do
+ local b, c, d, e, f
+ if bit32 then
+ b, c, d, e, f = bit32.band, bit32.rrotate, bit32.bxor, bit32.rshift, bit32.bnot
+ else
+ f = function(g)
+ g = math.floor(tonumber(g)) % 0x100000000
+ return (-g - 1) % 0x100000000
+ end
+ local h = {
+ [0] = { [0] = 0, 0, 0, 0 },
+ [1] = { [0] = 0, 1, 0, 1 },
+ [2] = { [0] = 0, 0, 2, 2 },
+ [3] = {
+ [0] = 0,
+ 1,
+ 2,
+ 3,
+ },
+ }
+ local i = {
+ [0] = { [0] = 0, 1, 2, 3 },
+ [1] = { [0] = 1, 0, 3, 2 },
+ [2] = { [0] = 2, 3, 0, 1 },
+ [3] = {
+ [0] = 3,
+ 2,
+ 1,
+ 0,
+ },
+ }
+ local function j(k, l, m, n, o)
+ for p = 1, m do
+ l[p] = math.floor(tonumber(l[p])) % 0x100000000
+ end
+ local q = 1
+ local r = 0
+ for s = 0, 31, 2 do
+ local t = n
+ for p = 1, m do
+ t = o[t][l[p] % 4]
+ l[p] = math.floor(l[p] / 4)
+ end
+ r = r + t * q
+ q = q * 4
+ end
+ return r
+ end
+ b = function(...)
+ return j("band", { ... }, select("#", ...), 3, h)
+ end
+ d = function(...)
+ return j("bxor", { ... }, select("#", ...), 0, i)
+ end
+ e = function(g, u)
+ g = math.floor(tonumber(g)) % 0x100000000
+ u = math.floor(tonumber(u))
+ u = math.min(math.max(-32, u), 32)
+ return math.floor(g / 2 ^ u) % 0x100000000
+ end
+ c = function(g, u)
+ g = math.floor(tonumber(g)) % 0x100000000
+ u = -math.floor(tonumber(u)) % 32
+ local g = g * 2 ^ u
+ return g % 0x100000000 + math.floor(g / 0x100000000)
+ end
+ end
+ local v = {
+ 0x428a2f98,
+ 0x71374491,
+ 0xb5c0fbcf,
+ 0xe9b5dba5,
+ 0x3956c25b,
+ 0x59f111f1,
+ 0x923f82a4,
+ 0xab1c5ed5,
+ 0xd807aa98,
+ 0x12835b01,
+ 0x243185be,
+ 0x550c7dc3,
+ 0x72be5d74,
+ 0x80deb1fe,
+ 0x9bdc06a7,
+ 0xc19bf174,
+ 0xe49b69c1,
+ 0xefbe4786,
+ 0x0fc19dc6,
+ 0x240ca1cc,
+ 0x2de92c6f,
+ 0x4a7484aa,
+ 0x5cb0a9dc,
+ 0x76f988da,
+ 0x983e5152,
+ 0xa831c66d,
+ 0xb00327c8,
+ 0xbf597fc7,
+ 0xc6e00bf3,
+ 0xd5a79147,
+ 0x06ca6351,
+ 0x14292967,
+ 0x27b70a85,
+ 0x2e1b2138,
+ 0x4d2c6dfc,
+ 0x53380d13,
+ 0x650a7354,
+ 0x766a0abb,
+ 0x81c2c92e,
+ 0x92722c85,
+ 0xa2bfe8a1,
+ 0xa81a664b,
+ 0xc24b8b70,
+ 0xc76c51a3,
+ 0xd192e819,
+ 0xd6990624,
+ 0xf40e3585,
+ 0x106aa070,
+ 0x19a4c116,
+ 0x1e376c08,
+ 0x2748774c,
+ 0x34b0bcb5,
+ 0x391c0cb3,
+ 0x4ed8aa4a,
+ 0x5b9cca4f,
+ 0x682e6ff3,
+ 0x748f82ee,
+ 0x78a5636f,
+ 0x84c87814,
+ 0x8cc70208,
+ 0x90befffa,
+ 0xa4506ceb,
+ 0xbef9a3f7,
+ 0xc67178f2,
+ }
+ local function w(n)
+ return string.gsub(n, ".", function(t)
+ return string.format("%02x", string.byte(t))
+ end)
+ end
+ local function x(y, z)
+ local n = ""
+ for p = 1, z do
+ local A = y % 256
+ n = string.char(A) .. n
+ y = (y - A) / 256
+ end
+ return n
+ end
+ local function B(n, p)
+ local z = 0
+ for p = p, p + 3 do
+ z = z * 256 + string.byte(n, p)
+ end
+ return z
+ end
+ local function C(D, E)
+ local F = -(E + 1 + 8) % 64
+ E = x(8 * E, 8)
+ D = D .. "\128" .. string.rep("\0", F) .. E
+ return D
+ end
+ local function G(H)
+ H[1] = 0x6a09e667
+ H[2] = 0xbb67ae85
+ H[3] = 0x3c6ef372
+ H[4] = 0xa54ff53a
+ H[5] = 0x510e527f
+ H[6] = 0x9b05688c
+ H[7] = 0x1f83d9ab
+ H[8] = 0x5be0cd19
+ return H
+ end
+ local function I(D, p, H)
+ local J = {}
+ for K = 1, 16 do
+ J[K] = B(D, p + (K - 1) * 4)
+ end
+ for K = 17, 64 do
+ local L = J[K - 15]
+ local M = d(c(L, 7), c(L, 18), e(L, 3))
+ L = J[K - 2]
+ local N = d(c(L, 17), c(L, 19), e(L, 10))
+ J[K] = J[K - 16] + M + J[K - 7] + N
+ end
+ local O, s, t, P, Q, R, S, T = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for p = 1, 64 do
+ local M = d(c(O, 2), c(O, 13), c(O, 22))
+ local U = d(b(O, s), b(O, t), b(s, t))
+ local V = M + U
+ local N = d(c(Q, 6), c(Q, 11), c(Q, 25))
+ local W = d(b(Q, R), b(f(Q), S))
+ local X = T + N + W + v[p] + J[p]
+ T = S
+ S = R
+ R = Q
+ Q = P + X
+ P = t
+ t = s
+ s = O
+ O = X + V
+ end
+ H[1] = b(H[1] + O)
+ H[2] = b(H[2] + s)
+ H[3] = b(H[3] + t)
+ H[4] = b(H[4] + P)
+ H[5] = b(H[5] + Q)
+ H[6] = b(H[6] + R)
+ H[7] = b(H[7] + S)
+ H[8] = b(H[8] + T)
+ end
+ local function Y(H)
+ return w(
+ x(H[1], 4) .. x(H[2], 4) .. x(H[3], 4) .. x(H[4], 4) .. x(H[5], 4) .. x(H[6], 4) .. x(H[7], 4) .. x(H[8], 4)
+ )
+ end
+ local Z = {}
+ sha256 = function(D)
+ D = C(D, #D)
+ local H = G(Z)
+ for p = 1, #D, 64 do
+ I(D, p, H)
+ end
+ return Y(H)
+ end
+end
+-- end of sha code
+
+gallery.ass_show = function(new_ass)
+ ass_changed = true
+ ass = new_ass
+end
+gallery.item_to_overlay_path = function(index, item)
+ local filename = item.filename
+ local filename_hash = hash_cache[filename]
+ if filename_hash == nil then
+ filename_hash = string.sub(sha256(normalize_path(filename)), 1, 12)
+ hash_cache[filename] = filename_hash
+ end
+ local thumb_filename = string.format(
+ "%s_%d_%d_%s",
+ filename_hash,
+ gallery.geometry.thumbnail_size[1],
+ gallery.geometry.thumbnail_size[2],
+ string.gsub(opts.take_thumbnail_at, "%%", "p")
+ )
+ return utils.join_path(thumbs_dir, thumb_filename)
+end
+gallery.item_to_thumbnail_params = function(index, item)
+ return item.filename, opts.take_thumbnail_at
+end
+function blend_colors(colors)
+ if #colors == 1 then
+ return colors[1]
+ end
+ local comp1 = 0
+ local comp2 = 0
+ local comp3 = 0
+ for _, val in ipairs(colors) do
+ comp1 = comp1 + tonumber(string.sub(val, 1, 2), 16)
+ comp2 = comp2 + tonumber(string.sub(val, 3, 4), 16)
+ comp3 = comp3 + tonumber(string.sub(val, 5, 6), 16)
+ end
+ return string.format("%02x%02x%02x", comp1 / #colors, comp2 / #colors, comp3 / #colors)
+end
+gallery.item_to_border = function(index, item)
+ local size = 0
+ colors = {}
+ if flags[item.filename] then
+ colors[#colors + 1] = opts.flagged_border_color
+ size = math.max(size, opts.flagged_border_size)
+ end
+ if index == gallery.selection then
+ colors[#colors + 1] = opts.selected_border_color
+ size = math.max(size, opts.selected_border_size)
+ end
+ if opts.highlight_active and index == playlist_pos then
+ colors[#colors + 1] = opts.active_border_color
+ size = math.max(size, opts.active_border_size)
+ end
+ if #colors == 0 then
+ return opts.normal_border_size, opts.normal_border_color
+ else
+ return size, blend_colors(colors)
+ end
+end
+gallery.item_to_text = function(index, item)
+ if not opts.show_text or index ~= gallery.selection then
+ return "", false
+ end
+ local f
+ if opts.show_title and item.title then
+ f = item.title
+ else
+ f = item.filename
+ if opts.strip_directory then
+ if ON_WINDOWS then
+ f = string.match(f, "([^\\/]+)$") or f
+ else
+ f = string.match(f, "([^/]+)$") or f
+ end
+ end
+ if opts.strip_extension then
+ f = string.match(f, "(.+)%.[^.]+$") or f
+ end
+ end
+ return f, true
+end
+
+function setup_ui_handlers()
+ for key, func in pairs(bindings_repeat) do
+ mp.add_forced_key_binding(key, "playlist-view-" .. key, func, { repeatable = true })
+ end
+ for key, func in pairs(bindings) do
+ mp.add_forced_key_binding(key, "playlist-view-" .. key, func)
+ end
+end
+
+function teardown_ui_handlers()
+ for key, _ in pairs(bindings_repeat) do
+ mp.remove_key_binding("playlist-view-" .. key)
+ end
+ for key, _ in pairs(bindings) do
+ mp.remove_key_binding("playlist-view-" .. key)
+ end
+end
+
+function reload_bindings()
+ if gallery.active then
+ teardown_ui_handlers()
+ end
+
+ bindings = {}
+ bindings_repeat = {}
+
+ local increment_func = function(increment, clamp)
+ local new = (pending_selection or gallery.selection) + increment
+ if new <= 0 or new > #gallery.items then
+ if not clamp then
+ return
+ end
+ new = math.max(1, math.min(new, #gallery.items))
+ end
+ pending_selection = new
+ end
+
+ bindings[opts.FIRST] = function()
+ pending_selection = 1
+ end
+ bindings[opts.LAST] = function()
+ pending_selection = #gallery.items
+ end
+ bindings[opts.ACCEPT] = function()
+ load_selection()
+ if opts.close_on_load_file then
+ stop()
+ end
+ end
+ bindings[opts.CANCEL] = function()
+ stop()
+ end
+ bindings[opts.FLAG] = function()
+ local name = gallery.items[gallery.selection].filename
+ if flags[name] == nil then
+ flags[name] = true
+ else
+ flags[name] = nil
+ end
+ gallery:ass_refresh(true, false, false, false)
+ end
+ if opts.mouse_support then
+ bindings["MBTN_LEFT"] = function()
+ local index = gallery:index_at(mp.get_mouse_pos())
+ if not index then
+ return
+ end
+ if index == gallery.selection then
+ load_selection()
+ if opts.close_on_load_file then
+ stop()
+ end
+ else
+ pending_selection = index
+ end
+ end
+ bindings["WHEEL_UP"] = function()
+ increment_func(-gallery.geometry.columns, false)
+ end
+ bindings["WHEEL_DOWN"] = function()
+ increment_func(gallery.geometry.columns, false)
+ end
+ end
+
+ bindings_repeat[opts.UP] = function()
+ increment_func(-gallery.geometry.columns, false)
+ end
+ bindings_repeat[opts.DOWN] = function()
+ increment_func(gallery.geometry.columns, false)
+ end
+ bindings_repeat[opts.LEFT] = function()
+ increment_func(-1, false)
+ end
+ bindings_repeat[opts.RIGHT] = function()
+ increment_func(1, false)
+ end
+ bindings_repeat[opts.PAGE_UP] = function()
+ increment_func(-gallery.geometry.columns * gallery.geometry.rows, true)
+ end
+ bindings_repeat[opts.PAGE_DOWN] = function()
+ increment_func(gallery.geometry.columns * gallery.geometry.rows, true)
+ end
+ bindings_repeat[opts.RANDOM] = function()
+ pending_selection = math.random(1, #gallery.items)
+ end
+ bindings_repeat[opts.REMOVE] = function()
+ local s = gallery.selection
+ mp.commandv("playlist-remove", s - 1)
+ gallery:set_selection(s + (s == #gallery.items and -1 or 1))
+ end
+
+ if gallery.active then
+ setup_ui_handlers()
+ end
+end
+
+function get_geometry_function()
+ local geometry_functions = loadstring(string.format(
+ [[
+ return {
+ function(ww, wh, gx, gy, gw, gh, sw, sh, tw, th)
+ return %s
+ end,
+ function(ww, wh, gx, gy, gw, gh, sw, sh, tw, th)
+ return %s
+ end,
+ function(ww, wh, gx, gy, gw, gh, sw, sh, tw, th)
+ return %s
+ end,
+ function(ww, wh, gx, gy, gw, gh, sw, sh, tw, th)
+ return %s
+ end
+ }]],
+ opts.gallery_position,
+ opts.gallery_size,
+ opts.min_spacing,
+ opts.thumbnail_size
+ ))()
+
+ local names = { "gallery_position", "gallery_size", "min_spacing", "thumbnail_size" }
+ local order = {} -- the order in which the 4 properties should be computed, based on inter-dependencies
+
+ -- build the dependency matrix
+ local patterns = { "g[xy]", "g[wh]", "s[wh]", "t[wh]" }
+ local deps = {}
+ for i = 1, 4 do
+ for j = 1, 4 do
+ local i_depends_on_j = (string.find(opts[names[i]], patterns[j]) ~= nil)
+ if i == j and i_depends_on_j then
+ msg.error(names[i] .. " depends on itself")
+ return
+ end
+ deps[i * 4 + j] = i_depends_on_j
+ end
+ end
+
+ local has_deps = function(index)
+ for j = 1, 4 do
+ if deps[index * 4 + j] then
+ return true
+ end
+ end
+ return false
+ end
+ local num_resolved = 0
+ local resolved = { false, false, false, false }
+ while true do
+ local resolved_one = false
+ for i = 1, 4 do
+ if resolved[i] then
+ -- nothing to do
+ elseif not has_deps(i) then
+ order[#order + 1] = i
+ -- since i has no deps, anything that depends on it might as well not
+ for j = 1, 4 do
+ deps[j * 4 + i] = false
+ end
+ resolved[i] = true
+ resolved_one = true
+ num_resolved = num_resolved + 1
+ end
+ end
+ if num_resolved == 4 then
+ break
+ elseif not resolved_one then
+ local str = ""
+ for index, resolved in ipairs(resolved) do
+ if not resolved then
+ str = (str == "" and "" or (str .. ", ")) .. names[index]
+ end
+ end
+ msg.error("Circular dependency between " .. str)
+ return
+ end
+ end
+
+ return function(window_width, window_height)
+ local new_geom = {
+ gallery_position = {},
+ gallery_size = {},
+ min_spacing = {},
+ thumbnail_size = {},
+ }
+ for _, index in ipairs(order) do
+ new_geom[names[index]] = geometry_functions[index](
+ window_width,
+ window_height,
+ new_geom.gallery_position[1],
+ new_geom.gallery_position[2],
+ new_geom.gallery_size[1],
+ new_geom.gallery_size[2],
+ new_geom.min_spacing[1],
+ new_geom.min_spacing[2],
+ new_geom.thumbnail_size[1],
+ new_geom.thumbnail_size[2]
+ )
+ -- extrawuerst
+ if opts.show_text and names[index] == "min_spacing" then
+ new_geom.min_spacing[2] = math.max(opts.text_size, new_geom.min_spacing[2])
+ elseif names[index] == "thumbnail_size" then
+ new_geom.thumbnail_size[1] = math.floor(new_geom.thumbnail_size[1])
+ new_geom.thumbnail_size[2] = math.floor(new_geom.thumbnail_size[2])
+ end
+ end
+ gallery:set_geometry(
+ new_geom.gallery_position[1],
+ new_geom.gallery_position[2],
+ new_geom.gallery_size[1],
+ new_geom.gallery_size[2],
+ new_geom.min_spacing[1],
+ new_geom.min_spacing[2],
+ new_geom.thumbnail_size[1],
+ new_geom.thumbnail_size[2]
+ )
+ end
+end
+
+function normalize_path(path)
+ if string.find(path, "://") then
+ return path
+ end
+ path = utils.join_path(utils.getcwd(), path)
+ if ON_WINDOWS then
+ path = string.gsub(path, "\\", "/")
+ end
+ path = string.gsub(path, "/%./", "/")
+ local n
+ repeat
+ path, n = string.gsub(path, "/[^/]*/%.%./", "/", 1)
+ until n == 0
+ return path
+end
+
+function playlist_changed(key, playlist)
+ if not gallery.active then
+ return
+ end
+ local did_change = function()
+ if #gallery.items ~= #playlist then
+ return true
+ end
+ for i = 1, #gallery.items do
+ if gallery.items[i].filename ~= playlist[i].filename then
+ return true
+ end
+ end
+ return false
+ end
+ if not did_change() then
+ return
+ end
+ if #playlist == 0 then
+ stop()
+ return
+ end
+ local selection_filename = gallery.items[gallery.selection].filename
+ gallery.items = playlist
+ local new_selection = math.max(1, math.min(gallery.selection, #gallery.items))
+ for i, f in ipairs(gallery.items) do
+ if selection_filename == f.filename then
+ new_selection = i
+ break
+ end
+ end
+ gallery:items_changed(new_selection)
+end
+
+function playlist_pos_changed(_, val)
+ playlist_pos = val
+ if opts.highlight_active then
+ gallery:ass_refresh(true, false, false, false)
+ end
+ if opts.follow_playlist_position then
+ pending_selection = val
+ end
+end
+
+function idle()
+ if pending_selection then
+ gallery:set_selection(pending_selection)
+ pending_selection = nil
+ end
+ if ass_changed or geometry_changed then
+ local ww, wh = mp.get_osd_size()
+ if geometry_changed then
+ geometry_changed = false
+ compute_geometry(ww, wh)
+ end
+ if ass_changed then
+ ass_changed = false
+ mp.set_osd_ass(ww, wh, ass)
+ end
+ end
+end
+
+function mark_geometry_stale()
+ geometry_changed = true
+end
+
+function start()
+ if gallery.active then
+ return
+ end
+ playlist = mp.get_property_native("playlist")
+ if #playlist == 0 then
+ return
+ end
+ gallery.items = playlist
+
+ local ww, wh = mp.get_osd_size()
+ compute_geometry(ww, wh)
+
+ playlist_pos = mp.get_property_number("playlist-pos-1")
+ gallery:set_selection(playlist_pos or 1)
+ if not gallery:activate() then
+ return
+ end
+
+ did_pause = false
+ if opts.pause_on_start and not mp.get_property_bool("pause", false) then
+ mp.set_property_bool("pause", true)
+ did_pause = true
+ end
+ if opts.command_on_open ~= "" then
+ mp.command(opts.command_on_open)
+ end
+ mp.observe_property("playlist-pos-1", "native", playlist_pos_changed)
+ mp.observe_property("playlist", "native", playlist_changed)
+ mp.observe_property("osd-width", "native", mark_geometry_stale)
+ mp.observe_property("osd-height", "native", mark_geometry_stale)
+ mp.register_idle(idle)
+ idle()
+
+ setup_ui_handlers()
+end
+
+function load_selection()
+ local sel = mp.get_property_number("playlist-pos-1", -1)
+ if sel == gallery.selection then
+ return
+ end
+ if opts.remember_time_position then
+ if sel then
+ local time = mp.get_property_number("time-pos")
+ if time and time > 1 then
+ resume[gallery.items[sel].filename] = time
+ end
+ end
+ mp.set_property("playlist-pos-1", gallery.selection)
+ local time = resume[gallery.items[gallery.selection].filename]
+ if not time then
+ return
+ end
+ local func
+ func = function()
+ mp.commandv("osd-msg-bar", "seek", time, "absolute")
+ mp.unregister_event(func)
+ end
+ mp.register_event("file-loaded", func)
+ else
+ mp.set_property("playlist-pos-1", gallery.selection)
+ end
+end
+
+function stop()
+ if not gallery.active then
+ return
+ end
+ if opts.resume_on_stop == "yes" or (opts.resume_on_stop == "only-if-did-pause" and did_pause) then
+ mp.set_property_bool("pause", false)
+ end
+ if opts.command_on_close ~= "" then
+ mp.command(opts.command_on_close)
+ end
+ mp.unobserve_property(playlist_pos_changed)
+ mp.unobserve_property(playlist_changed)
+ mp.unobserve_property(mark_geometry_stale)
+ mp.unregister_idle(idle)
+ teardown_ui_handlers()
+ gallery:deactivate()
+ idle()
+end
+
+function toggle()
+ if not gallery.active then
+ start()
+ else
+ if opts.load_file_on_toggle_off then
+ load_selection()
+ end
+ stop()
+ end
+end
+
+mp.register_script_message("thumbnail-generated", function(thumb_path)
+ gallery:thumbnail_generated(thumb_path)
+end)
+
+mp.register_script_message("thumbnails-generator-broadcast", function(generator_name)
+ gallery:add_generator(generator_name)
+end)
+
+function write_flag_file()
+ if next(flags) == nil then
+ return
+ end
+ local out = io.open(opts.flagged_file_path, "w")
+ for f, _ in pairs(flags) do
+ out:write(f .. "\n")
+ end
+ out:close()
+end
+mp.register_event("shutdown", write_flag_file)
+
+reload_config()
+
+if opts.start_on_file_end then
+ mp.observe_property("eof-reached", "bool", function(_, val)
+ if val and mp.get_property_number("playlist-count") > 1 then
+ start()
+ end
+ end)
+end
+
+if opts.start_on_mpv_startup then
+ local autostart
+ autostart = function()
+ if mp.get_property_number("playlist-count") == 0 then
+ return
+ end
+ if mp.get_property_number("osd-width") <= 0 then
+ return
+ end
+ start()
+ mp.unobserve_property(autostart)
+ end
+ mp.observe_property("playlist-count", "number", autostart)
+ mp.observe_property("osd-width", "number", autostart)
+end
+
+mp.add_key_binding(nil, "playlist-view-open", function()
+ start()
+end)
+mp.add_key_binding(nil, "playlist-view-close", stop)
+mp.add_key_binding("g", "playlist-view-toggle", toggle)
+mp.add_key_binding(nil, "playlist-view-load-selection", load_selection)
+mp.add_key_binding(nil, "playlist-view-write-flag-file", write_flag_file)
diff --git a/ar/.config/mpv/scripts/playlistmanager.lua b/ar/.config/mpv/scripts/playlistmanager.lua
new file mode 100644
index 0000000..a0e20ed
--- /dev/null
+++ b/ar/.config/mpv/scripts/playlistmanager.lua
@@ -0,0 +1,1755 @@
+local settings = {
+
+ -- #### FUNCTIONALITY SETTINGS
+
+ --navigation keybindings force override only while playlist is visible
+ --if "no" then you can display the playlist by any of the navigation keys
+ dynamic_binds = true,
+
+ -- to bind multiple keys separate them by a space
+
+ -- main key to show playlist
+ key_showplaylist = "v",
+
+ -- display playlist while key is held down
+ key_peek_at_playlist = "",
+
+ -- dynamic keys
+ key_moveup = "k",
+ key_movedown = "j",
+ key_movepageup = "ctrl+u",
+ key_movepagedown = "ctrl+d",
+ key_movebegin = "g",
+ key_moveend = "G",
+ key_selectfile = "space",
+ key_unselectfile = "ctrl+space",
+ key_playfile = "ENTER",
+ key_removefile = "DEL",
+ key_closeplaylist = "ESC q ctrl+c",
+
+ -- extra functionality keys
+ key_sortplaylist = "shift+s",
+ key_shuffleplaylist = "ctrl+s",
+ key_reverseplaylist = "BS",
+ key_loadfiles = "alt+r",
+ key_saveplaylist = "INS",
+
+ --replaces matches on filenames based on extension, put as empty string to not replace anything
+ --replace rules are executed in provided order
+ --replace rule key is the pattern and value is the replace value
+ --uses :gsub('pattern', 'replace'), read more http://lua-users.org/wiki/StringLibraryTutorial
+ --'all' will match any extension or protocol if it has one
+ --uses json and parses it into a lua table to be able to support .conf file
+
+ filename_replace = [[
+ [
+ {
+ "protocol": { "all": true },
+ "rules": [
+ { "%%(%x%x)": "hex_to_char" }
+ ]
+ }
+ ]
+ ]],
+
+ --[=====[ START OF SAMPLE REPLACE - Remove this line to use it
+ --Sample replace: replaces underscore to space on all files
+ --for mp4 and webm; remove extension, remove brackets and surrounding whitespace, change dot between alphanumeric to space
+ filename_replace = [[
+ [
+ {
+ "ext": { "all": true},
+ "rules": [
+ { "_" : " " }
+ ]
+ },{
+ "ext": { "mp4": true, "mkv": true },
+ "rules": [
+ { "^(.+)%..+$": "%1" },
+ { "%s*[%[%(].-[%]%)]%s*": "" },
+ { "(%w)%.(%w)": "%1 %2" }
+ ]
+ },{
+ "protocol": { "http": true, "https": true },
+ "rules": [
+ { "^%a+://w*%.?": "" }
+ ]
+ }
+ ]
+ ]],
+--END OF SAMPLE REPLACE ]=====]
+
+ --json array of filetypes to search from directory
+ loadfiles_filetypes = [[
+ [
+ "jpg", "jpeg", "png", "tif", "tiff", "gif", "webp", "svg", "bmp",
+ "mp3", "wav", "ogm", "flac", "m4a", "wma", "ogg", "opus",
+ "mkv", "avi", "mp4", "ogv", "webm", "rmvb", "flv", "wmv", "mpeg", "mpg", "m4v", "3gp"
+ ]
+ ]],
+
+ --loadfiles at startup if 1 or more items in playlist
+ loadfiles_on_start = false,
+ -- loadfiles from working directory on idle startup
+ loadfiles_on_idle_start = false,
+ --always put loaded files after currently playing file
+ loadfiles_always_append = false,
+
+ --sort playlist when files are added to playlist
+ sortplaylist_on_file_add = false,
+
+ --default sorting method, must be one of: "name-asc", "name-desc", "date-asc", "date-desc", "size-asc", "size-desc".
+ default_sort = "name-asc",
+
+ --"linux | windows | auto"
+ system = "auto",
+
+ --Use ~ for home directory. Leave as empty to use mpv/playlists
+ playlist_savepath = "",
+
+ -- constant filename to save playlist as. Note that it will override existing playlist. Leave empty for generated name.
+ playlist_save_filename = "",
+
+ --save playlist automatically after current file was unloaded
+ save_playlist_on_file_end = false,
+
+ --show file title every time a new file is loaded
+ show_title_on_file_load = false,
+ --show playlist every time a new file is loaded
+ show_playlist_on_file_load = false,
+ --close playlist when selecting file to play
+ close_playlist_on_playfile = false,
+
+ --sync cursor when file is loaded from outside reasons(file-ending, playlist-next shortcut etc.)
+ --has the sideeffect of moving cursor if file happens to change when navigating
+ --good side is cursor always following current file when going back and forth files with playlist-next/prev
+ sync_cursor_on_load = true,
+
+ --allow the playlist cursor to loop from end to start and vice versa
+ loop_cursor = true,
+
+ --youtube-dl executable for title resolving if enabled, probably "youtube-dl" or "yt-dlp", can be absolute path
+ youtube_dl_executable = "yt-dlp",
+
+ -- allow playlistmanager to write watch later config when navigating between files
+ allow_write_watch_later_config = true,
+
+ -- reset cursor navigation when closing or opening playlist
+ reset_cursor_on_close = true,
+ reset_cursor_on_open = true,
+
+ --#### VISUAL SETTINGS
+
+ --prefer to display titles for following files: "all", "url", "none". Sorting still uses filename.
+ prefer_titles = "url",
+
+ --call youtube-dl to resolve the titles of urls in the playlist
+ resolve_url_titles = false,
+
+ --call ffprobe to resolve the titles of local files in the playlist (if they exist in the metadata)
+ resolve_local_titles = false,
+
+ -- timeout in seconds for url title resolving
+ resolve_title_timeout = 15,
+
+ -- how many url titles can be resolved at a time. Higher number might lead to stutters.
+ concurrent_title_resolve_limit = 10,
+
+ --osd timeout on inactivity in seconds, use 0 for no timeout
+ playlist_display_timeout = 0,
+
+ -- when peeking at playlist, show playlist at the very least for display timeout
+ peek_respect_display_timeout = false,
+
+ -- the maximum amount of lines playlist will render. -1 will automatically calculate lines.
+ showamount = -1,
+
+ --playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
+ --example {\\q2\\an7\\fnUbuntu\\fs10\\b0\\bord1} equals: line-wrap=no, align=top left, font=Ubuntu, size=10, bold=no, border=1
+ --read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
+ --undeclared tags will use default osd settings
+ --these styles will be used for the whole playlist
+ --\\q2 style is recommended since filename wrapping may lead to unexpected rendering
+ --\\an7 style is recommended to align to top left otherwise, osd-align-x/y is respected
+ style_ass_tags = "{\\q2\\an7\\fnUbuntu\\fs20\\b0\\bord1\\c&HFFFFFF&}",
+
+ --paddings for left right and top bottom, depends on alignment
+ text_padding_x = 30,
+ text_padding_y = 60,
+
+ --screen dim when menu is open 0.0 - 1.0 (0 is no dim, 1 is black)
+ curtain_opacity = 0.0,
+
+ --set title of window with stripped name
+ set_title_stripped = false,
+ title_prefix = "",
+ title_suffix = " - mpv",
+
+ --slice long filenames, and how many chars to show
+ slice_longfilenames = false,
+ slice_longfilenames_amount = 70,
+
+ --Playlist header template
+ --%mediatitle or %filename = title or name of playing file
+ --%pos = position of playing file
+ --%cursor = position of navigation
+ --%plen = playlist length
+ --%N = newline
+ playlist_header = "[%cursor/%plen]",
+
+ --Playlist file templates
+ --%pos = position of file with leading zeros
+ --%name = title or name of file
+ --%N = newline
+ --you can also use the ass tags mentioned above. For example:
+ -- selected_file="{\\c&HFF00FF&}➔ %name" | to add a color for selected file. However, if you
+ -- use ass tags you need to reset them for every line (see https://github.com/jonniek/mpv-playlistmanager/issues/20)
+ normal_file = "{\\c&HFFFFFF&}○ %name",
+ hovered_file = "{\\c&H0080FF&}● %name",
+ selected_file = "{\\c&H00FF00&}➔ %name",
+ playing_file = "{\\c&HFF00FF&}▷ %name",
+ playing_hovered_file = "{\\c&H7F40FF&}▶ %name",
+ playing_selected_file = "{\\c&H00BF7F&}➤ %name",
+
+ -- what to show when playlist is truncated
+ playlist_sliced_prefix = "...",
+ playlist_sliced_suffix = "...",
+
+ --output visual feedback to OSD for tasks
+ display_osd_feedback = true,
+}
+local opts = require("mp.options")
+opts.read_options(settings, "playlistmanager", function(list)
+ update_opts(list)
+end)
+
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+local assdraw = require("mp.assdraw")
+
+local alignment_table = {
+ [1] = { ["x"] = "left", ["y"] = "bottom" },
+ [2] = { ["x"] = "center", ["y"] = "bottom" },
+ [3] = { ["x"] = "right", ["y"] = "bottom" },
+ [4] = { ["x"] = "left", ["y"] = "center" },
+ [5] = { ["x"] = "center", ["y"] = "center" },
+ [6] = { ["x"] = "right", ["y"] = "center" },
+ [7] = { ["x"] = "left", ["y"] = "top" },
+ [8] = { ["x"] = "center", ["y"] = "top" },
+ [9] = { ["x"] = "right", ["y"] = "top" },
+}
+
+--check os
+if settings.system == "auto" then
+ local o = {}
+ if mp.get_property_native("options/vo-mmcss-profile", o) ~= o then
+ settings.system = "windows"
+ else
+ settings.system = "linux"
+ end
+end
+
+-- auto calculate showamount
+if settings.showamount == -1 then
+ -- same as draw_playlist() height
+ local h = 720
+
+ local playlist_h = h
+ -- both top and bottom with same padding
+ playlist_h = playlist_h - settings.text_padding_y * 2
+
+ -- osd-font-size is based on 720p height
+ -- see https://mpv.io/manual/stable/#options-osd-font-size
+ -- details in https://mpv.io/manual/stable/#options-sub-font-size
+ -- draw_playlist() is based on 720p, need some conversion
+ local fs = mp.get_property_native("osd-font-size") * h / 720
+ -- get the ass font size
+ if settings.style_ass_tags ~= nil then
+ local ass_fs_tag = settings.style_ass_tags:match("\\fs%d+")
+ if ass_fs_tag ~= nil then
+ fs = tonumber(ass_fs_tag:match("%d+"))
+ end
+ end
+
+ settings.showamount = math.floor(playlist_h / fs)
+
+ -- exclude the header line
+ if settings.playlist_header ~= "" then
+ settings.showamount = settings.showamount - 1
+ -- probably some newlines (%N or \N) in the header
+ for _ in settings.playlist_header:gmatch("%%N") do
+ settings.showamount = settings.showamount - 1
+ end
+ for _ in settings.playlist_header:gmatch("\\N") do
+ settings.showamount = settings.showamount - 1
+ end
+ end
+
+ msg.info("auto showamount: " .. settings.showamount)
+end
+
+--global variables
+local playlist_overlay = mp.create_osd_overlay("ass-events")
+local playlist_visible = false
+local strippedname = nil
+local path = nil
+local directory = nil
+local filename = nil
+local pos = 0
+local plen = 0
+local cursor = 0
+--table for saved media titles for later if we prefer them
+local title_table = {}
+-- table for urls and local file paths that we have requested to be resolved to titles
+local requested_titles = {}
+
+local filetype_lookup = {}
+
+function refresh_UI()
+ if not playlist_visible then
+ return
+ end
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ draw_playlist()
+end
+
+function update_opts(changelog)
+ msg.verbose("updating options")
+
+ --parse filename json
+ if changelog.filename_replace then
+ if settings.filename_replace ~= "" then
+ settings.filename_replace = utils.parse_json(settings.filename_replace)
+ else
+ settings.filename_replace = false
+ end
+ end
+
+ --parse loadfiles json
+ if changelog.loadfiles_filetypes then
+ settings.loadfiles_filetypes = utils.parse_json(settings.loadfiles_filetypes)
+
+ filetype_lookup = {}
+ --create loadfiles set
+ for _, ext in ipairs(settings.loadfiles_filetypes) do
+ filetype_lookup[ext] = true
+ end
+ end
+
+ if changelog.resolve_url_titles then
+ resolve_titles()
+ end
+
+ if changelog.resolve_local_titles then
+ resolve_titles()
+ end
+
+ if changelog.playlist_display_timeout then
+ keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
+ keybindstimer:kill()
+ end
+
+ refresh_UI()
+end
+
+update_opts({ filename_replace = true, loadfiles_filetypes = true })
+
+----- winapi start -----
+-- in windows system, we can use the sorting function provided by the win32 API
+-- see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw
+local winapisort = nil
+if settings.system == "windows" then
+ -- ffiok is false usually means the mpv builds without luajit
+ local ffiok, ffi = pcall(require, "ffi")
+ if ffiok then
+ ffi.cdef([[
+ int MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
+ int StrCmpLogicalW(const wchar_t * psz1, const wchar_t * psz2);
+ ]])
+
+ local shlwapi = ffi.load("shlwapi.dll")
+
+ function MultiByteToWideChar(MultiByteStr)
+ local UTF8_CODEPAGE = 65001
+ if MultiByteStr then
+ local utf16_len = ffi.C.MultiByteToWideChar(UTF8_CODEPAGE, 0, MultiByteStr, -1, nil, 0)
+ if utf16_len > 0 then
+ local utf16_str = ffi.new("wchar_t[?]", utf16_len)
+ if ffi.C.MultiByteToWideChar(UTF8_CODEPAGE, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then
+ return utf16_str
+ end
+ end
+ end
+ return ""
+ end
+
+ winapisort = function(a, b)
+ return shlwapi.StrCmpLogicalW(MultiByteToWideChar(a), MultiByteToWideChar(b)) < 0
+ end
+ end
+end
+----- winapi end -----
+
+local sort_modes = {
+ {
+ id = "name-asc",
+ title = "name ascending",
+ sort_fn = function(a, b, playlist)
+ if winapisort ~= nil then
+ return winapisort(playlist[a].string, playlist[b].string)
+ end
+ return alphanumsort(playlist[a].string, playlist[b].string)
+ end,
+ },
+ {
+ id = "name-desc",
+ title = "name descending",
+ sort_fn = function(a, b, playlist)
+ if winapisort ~= nil then
+ return winapisort(playlist[b].string, playlist[a].string)
+ end
+ return alphanumsort(playlist[b].string, playlist[a].string)
+ end,
+ },
+ {
+ id = "date-asc",
+ title = "date ascending",
+ sort_fn = function(a, b)
+ return (get_file_info(a).mtime or 0) < (get_file_info(b).mtime or 0)
+ end,
+ },
+ {
+ id = "date-desc",
+ title = "date descending",
+ sort_fn = function(a, b)
+ return (get_file_info(a).mtime or 0) > (get_file_info(b).mtime or 0)
+ end,
+ },
+ {
+ id = "size-asc",
+ title = "size ascending",
+ sort_fn = function(a, b)
+ return (get_file_info(a).size or 0) < (get_file_info(b).size or 0)
+ end,
+ },
+ {
+ id = "size-desc",
+ title = "size descending",
+ sort_fn = function(a, b)
+ return (get_file_info(a).size or 0) > (get_file_info(b).size or 0)
+ end,
+ },
+}
+
+local sort_mode = 1
+for mode, sort_data in pairs(sort_modes) do
+ if sort_data.id == settings.default_sort then
+ sort_mode = mode
+ end
+end
+
+function is_protocol(path)
+ return type(path) == "string" and path:match("^%a[%a%d-_]+://") ~= nil
+end
+
+function on_file_loaded()
+ refresh_globals()
+ if settings.sync_cursor_on_load then
+ cursor = pos
+ end
+ refresh_UI() -- refresh only after moving cursor
+
+ filename = mp.get_property("filename")
+ path = mp.get_property("path")
+ local media_title = mp.get_property("media-title")
+ if is_protocol(path) and not title_table[path] and path ~= media_title then
+ title_table[path] = media_title
+ end
+
+ strippedname = stripfilename(mp.get_property("media-title"))
+ if settings.show_title_on_file_load then
+ mp.commandv("show-text", strippedname)
+ end
+ if settings.show_playlist_on_file_load then
+ showplaylist()
+ end
+ if settings.set_title_stripped then
+ mp.set_property("title", settings.title_prefix .. strippedname .. settings.title_suffix)
+ end
+end
+
+function on_start_file()
+ refresh_globals()
+ filename = mp.get_property("filename")
+ path = mp.get_property("path")
+ --if not a url then join path with working directory
+ if not is_protocol(path) then
+ path = utils.join_path(mp.get_property("working-directory"), path)
+ directory = utils.split_path(path)
+ else
+ directory = nil
+ end
+
+ if settings.loadfiles_on_start and plen == 1 then
+ local ext = filename:match("%.([^%.]+)$")
+ -- a directory or playlist has been loaded, let's not do anything as mpv will expand it into files
+ if ext and filetype_lookup[ext:lower()] then
+ msg.info("Loading files from playing files directory")
+ playlist()
+ end
+ end
+end
+
+function on_end_file()
+ if settings.save_playlist_on_file_end then
+ save_playlist()
+ end
+ strippedname = nil
+ path = nil
+ directory = nil
+ filename = nil
+end
+
+function refresh_globals()
+ pos = mp.get_property_number("playlist-pos", 0)
+ plen = mp.get_property_number("playlist-count", 0)
+end
+
+function escapepath(dir, escapechar)
+ return string.gsub(dir, escapechar, "\\" .. escapechar)
+end
+
+function replace_table_has_value(value, valid_values)
+ if value == nil or valid_values == nil then
+ return false
+ end
+ return valid_values["all"] or valid_values[value]
+end
+
+local filename_replace_functions = {
+ --decode special characters in url
+ hex_to_char = function(x)
+ return string.char(tonumber(x, 16))
+ end,
+}
+
+--strip a filename based on its extension or protocol according to rules in settings
+function stripfilename(pathfile, media_title)
+ if pathfile == nil then
+ return ""
+ end
+ local ext = pathfile:match("%.([^%.]+)$")
+ local protocol = pathfile:match("^(%a%a+)://")
+ if not ext then
+ ext = ""
+ end
+ local tmp = pathfile
+ if settings.filename_replace and not media_title then
+ for k, v in ipairs(settings.filename_replace) do
+ if replace_table_has_value(ext, v["ext"]) or replace_table_has_value(protocol, v["protocol"]) then
+ for ruleindex, indexrules in ipairs(v["rules"]) do
+ for rule, override in pairs(indexrules) do
+ override = filename_replace_functions[override] or override
+ tmp = tmp:gsub(rule, override)
+ end
+ end
+ end
+ end
+ end
+ if settings.slice_longfilenames and tmp:len() > settings.slice_longfilenames_amount + 5 then
+ tmp = tmp:sub(1, settings.slice_longfilenames_amount) .. " ..."
+ end
+ return tmp
+end
+
+--gets the file info of an item
+function get_file_info(item)
+ local path = mp.get_property("playlist/" .. item - 1 .. "/filename")
+ if is_protocol(path) then
+ return {}
+ end
+ local file_info = utils.file_info(path)
+ if not file_info then
+ msg.warn("failed to read file info for", path)
+ return {}
+ end
+
+ return file_info
+end
+
+--gets a nicename of playlist entry at 0-based position i
+function get_name_from_index(i, notitle)
+ refresh_globals()
+ if plen <= i then
+ msg.error("no index in playlist", i, "length", plen)
+ return nil
+ end
+ local _, name = nil
+ local title = mp.get_property("playlist/" .. i .. "/title")
+ local name = mp.get_property("playlist/" .. i .. "/filename")
+
+ local should_use_title = settings.prefer_titles == "all" or is_protocol(name) and settings.prefer_titles == "url"
+ --check if file has a media title stored or as property
+ if not title and should_use_title then
+ local mtitle = mp.get_property("media-title")
+ if i == pos and mp.get_property("filename") ~= mtitle then
+ if not title_table[name] then
+ title_table[name] = mtitle
+ end
+ title = mtitle
+ elseif title_table[name] then
+ title = title_table[name]
+ end
+ end
+
+ --if we have media title use a more conservative strip
+ if title and not notitle and should_use_title then
+ -- Escape a string for verbatim display on the OSD
+ -- Ref: https://github.com/mpv-player/mpv/blob/94677723624fb84756e65c8f1377956667244bc9/player/lua/stats.lua#L145
+ return stripfilename(title, true):gsub("\\", "\\\239\187\191"):gsub("{", "\\{"):gsub("^ ", "\\h")
+ end
+
+ --remove paths if they exist, keeping protocols for stripping
+ if string.sub(name, 1, 1) == "/" or name:match("^%a:[/\\]") then
+ _, name = utils.split_path(name)
+ end
+ return stripfilename(name):gsub("\\", "\\\239\187\191"):gsub("{", "\\{"):gsub("^ ", "\\h")
+end
+
+function parse_header(string)
+ local esc_title = stripfilename(mp.get_property("media-title"), true):gsub("%%", "%%%%")
+ local esc_file = stripfilename(mp.get_property("filename")):gsub("%%", "%%%%")
+ return string
+ :gsub("%%N", "\\N")
+ -- add a blank character at the end of each '\N' to ensure that the height of the empty line is the same as the non empty line
+ :gsub(
+ "\\N",
+ "\\N "
+ )
+ :gsub("%%pos", mp.get_property_number("playlist-pos", 0) + 1)
+ :gsub("%%plen", mp.get_property("playlist-count"))
+ :gsub("%%cursor", cursor + 1)
+ :gsub("%%mediatitle", esc_title)
+ :gsub("%%filename", esc_file)
+ -- undo name escape
+ :gsub("%%%%", "%%")
+end
+
+function parse_filename(string, name, index)
+ local base = tostring(plen):len()
+ local esc_name = stripfilename(name):gsub("%%", "%%%%")
+ return string
+ :gsub("%%N", "\\N")
+ :gsub("%%pos", string.format("%0" .. base .. "d", index + 1))
+ :gsub("%%name", esc_name)
+ -- undo name escape
+ :gsub("%%%%", "%%")
+end
+
+function parse_filename_by_index(index)
+ local template = settings.normal_file
+
+ local is_idle = mp.get_property_native("idle-active")
+ local position = is_idle and -1 or pos
+
+ if index == position then
+ if index == cursor then
+ if selection then
+ template = settings.playing_selected_file
+ else
+ template = settings.playing_hovered_file
+ end
+ else
+ template = settings.playing_file
+ end
+ elseif index == cursor then
+ if selection then
+ template = settings.selected_file
+ else
+ template = settings.hovered_file
+ end
+ end
+
+ return parse_filename(template, get_name_from_index(index), index)
+end
+
+function draw_playlist()
+ refresh_globals()
+ local ass = assdraw.ass_new()
+
+ local _, _, a = mp.get_osd_size()
+ local h = 720
+ local w = math.ceil(h * a)
+
+ if settings.curtain_opacity ~= nil and settings.curtain_opacity ~= 0 and settings.curtain_opacity <= 1.0 then
+ -- curtain dim from https://github.com/christoph-heinrich/mpv-quality-menu/blob/501794bfbef468ee6a61e54fc8821fe5cd72c4ed/quality-menu.lua#L699-L707
+ local alpha = 255 - math.ceil(255 * settings.curtain_opacity)
+ ass.text = string.format("{\\pos(0,0)\\rDefault\\an7\\1c&H000000&\\alpha&H%X&}", alpha)
+ ass:draw_start()
+ ass:rect_cw(0, 0, w, h)
+ ass:draw_stop()
+ ass:new_event()
+ end
+
+ ass:append(settings.style_ass_tags)
+
+ -- align from mpv.conf
+ local align_x = mp.get_property("osd-align-x")
+ local align_y = mp.get_property("osd-align-y")
+ -- align from style_ass_tags
+ if settings.style_ass_tags ~= nil then
+ local an = tonumber(settings.style_ass_tags:match("\\an(%d)"))
+ if an ~= nil and alignment_table[an] ~= nil then
+ align_x = alignment_table[an]["x"]
+ align_y = alignment_table[an]["y"]
+ end
+ end
+ -- range of x [0, w-1]
+ local pos_x
+ if align_x == "left" then
+ pos_x = settings.text_padding_x
+ elseif align_x == "right" then
+ pos_x = w - 1 - settings.text_padding_x
+ else
+ pos_x = math.floor((w - 1) / 2)
+ end
+ -- range of y [0, h-1]
+ local pos_y
+ if align_y == "top" then
+ pos_y = settings.text_padding_y
+ elseif align_y == "bottom" then
+ pos_y = h - 1 - settings.text_padding_y
+ else
+ pos_y = math.floor((h - 1) / 2)
+ end
+ ass:pos(pos_x, pos_y)
+
+ if settings.playlist_header ~= "" then
+ ass:append(parse_header(settings.playlist_header) .. "\\N")
+ end
+
+ -- (visible index, playlist index) pairs of playlist entries that should be rendered
+ local visible_indices = {}
+
+ local one_based_cursor = cursor + 1
+ table.insert(visible_indices, one_based_cursor)
+
+ local offset = 1
+ local visible_indices_length = 1
+ while visible_indices_length < settings.showamount and visible_indices_length < plen do
+ -- add entry for offset steps below the cursor
+ local below = one_based_cursor + offset
+ if below <= plen then
+ table.insert(visible_indices, below)
+ visible_indices_length = visible_indices_length + 1
+ end
+
+ -- add entry for offset steps above the cursor
+ -- also need to double check that there is still space, this happens if we have even numbered limit
+ local above = one_based_cursor - offset
+ if above >= 1 and visible_indices_length < settings.showamount and visible_indices_length < plen then
+ table.insert(visible_indices, 1, above)
+ visible_indices_length = visible_indices_length + 1
+ end
+
+ offset = offset + 1
+ end
+
+ -- both indices are 1 based
+ for display_index, playlist_index in pairs(visible_indices) do
+ if display_index == 1 and playlist_index ~= 1 then
+ ass:append(settings.playlist_sliced_prefix .. "\\N")
+ elseif display_index == settings.showamount and playlist_index ~= plen then
+ ass:append(settings.playlist_sliced_suffix)
+ else
+ -- parse_filename_by_index expects 0 based index
+ ass:append(parse_filename_by_index(playlist_index - 1) .. "\\N")
+ end
+ end
+
+ playlist_overlay.data = ass.text
+ playlist_overlay:update()
+end
+
+local peek_display_timer = nil
+local peek_button_pressed = false
+
+function peek_timeout()
+ peek_display_timer:kill()
+ if not peek_button_pressed and not playlist_visible then
+ remove_keybinds()
+ end
+end
+
+function handle_complex_playlist_toggle(table)
+ local event = table["event"]
+ if event == "press" then
+ msg.error("Complex key event not supported. Falling back to normal playlist display.")
+ showplaylist()
+ elseif event == "down" then
+ showplaylist(1000000)
+ if settings.peek_respect_display_timeout then
+ peek_button_pressed = true
+ peek_display_timer = mp.add_periodic_timer(settings.playlist_display_timeout, peek_timeout)
+ end
+ elseif event == "up" then
+ -- set playlist state to not visible, doesn't actually hide playlist yet
+ -- this will allow us to check if other functionality has rendered playlist before removing binds
+ playlist_visible = false
+
+ function remove_keybinds_after_timeout()
+ -- if playlist is still not visible then lets actually hide it
+ -- this lets other keys that interupt the peek to render playlist without peek up event closing it
+ if not playlist_visible then
+ remove_keybinds()
+ end
+ end
+
+ if settings.peek_respect_display_timeout then
+ peek_button_pressed = false
+ if not peek_display_timer:is_enabled() then
+ mp.add_timeout(0.01, remove_keybinds_after_timeout)
+ end
+ else
+ -- use small delay to let dynamic binds run before keys are potentially unbound
+ mp.add_timeout(0.01, remove_keybinds_after_timeout)
+ end
+ end
+end
+
+function toggle_playlist(show_function)
+ local show = show_function or showplaylist
+ if playlist_visible then
+ remove_keybinds()
+ else
+ -- toggle always shows without timeout
+ show(0)
+ end
+end
+
+function showplaylist(duration)
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ if not playlist_visible and settings.reset_cursor_on_open then
+ resetcursor()
+ end
+
+ playlist_visible = true
+ add_keybinds()
+
+ draw_playlist()
+ keybindstimer:kill()
+
+ local dur = tonumber(duration) or settings.playlist_display_timeout
+ if dur > 0 then
+ keybindstimer = mp.add_periodic_timer(dur, remove_keybinds)
+ end
+end
+
+function showplaylist_non_interactive(duration)
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ if not playlist_visible and settings.reset_cursor_on_open then
+ resetcursor()
+ end
+ playlist_visible = true
+ draw_playlist()
+ keybindstimer:kill()
+
+ local dur = tonumber(duration) or settings.playlist_display_timeout
+ if dur > 0 then
+ keybindstimer = mp.add_periodic_timer(dur, remove_keybinds)
+ end
+end
+
+selection = nil
+function selectfile()
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ if not selection then
+ selection = cursor
+ else
+ selection = nil
+ end
+ showplaylist()
+end
+
+function unselectfile()
+ selection = nil
+ showplaylist()
+end
+
+function resetcursor()
+ selection = nil
+ cursor = mp.get_property_number("playlist-pos", 1)
+end
+
+function removefile()
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ selection = nil
+ if cursor == pos then
+ mp.command('script-message unseenplaylist mark true "playlistmanager avoid conflict when removing file"')
+ end
+ mp.commandv("playlist-remove", cursor)
+ if cursor == plen - 1 then
+ cursor = cursor - 1
+ end
+ if plen == 1 then
+ remove_keybinds()
+ else
+ showplaylist()
+ end
+end
+
+function moveup()
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ if cursor ~= 0 then
+ if selection then
+ mp.commandv("playlist-move", cursor, cursor - 1)
+ end
+ cursor = cursor - 1
+ elseif settings.loop_cursor then
+ if selection then
+ mp.commandv("playlist-move", cursor, plen)
+ end
+ cursor = plen - 1
+ end
+ showplaylist()
+end
+
+function movedown()
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ if cursor ~= plen - 1 then
+ if selection then
+ mp.commandv("playlist-move", cursor, cursor + 2)
+ end
+ cursor = cursor + 1
+ elseif settings.loop_cursor then
+ if selection then
+ mp.commandv("playlist-move", cursor, 0)
+ end
+ cursor = 0
+ end
+ showplaylist()
+end
+
+function movepageup()
+ refresh_globals()
+ if plen == 0 or cursor == 0 then
+ return
+ end
+ local prev_cursor = cursor
+ cursor = cursor - settings.showamount
+ if cursor < 0 then
+ cursor = 0
+ end
+ if selection then
+ mp.commandv("playlist-move", prev_cursor, cursor)
+ end
+ showplaylist()
+end
+
+function movepagedown()
+ refresh_globals()
+ if plen == 0 or cursor == plen - 1 then
+ return
+ end
+ local prev_cursor = cursor
+ cursor = cursor + settings.showamount
+ if cursor >= plen then
+ cursor = plen - 1
+ end
+ if selection then
+ mp.commandv("playlist-move", prev_cursor, cursor + 1)
+ end
+ showplaylist()
+end
+
+function movebegin()
+ refresh_globals()
+ if plen == 0 or cursor == 0 then
+ return
+ end
+ local prev_cursor = cursor
+ cursor = 0
+ if selection then
+ mp.commandv("playlist-move", prev_cursor, cursor)
+ end
+ showplaylist()
+end
+
+function moveend()
+ refresh_globals()
+ if plen == 0 or cursor == plen - 1 then
+ return
+ end
+ local prev_cursor = cursor
+ cursor = plen - 1
+ if selection then
+ mp.commandv("playlist-move", prev_cursor, cursor + 1)
+ end
+ showplaylist()
+end
+
+function write_watch_later(force_write)
+ if settings.allow_write_watch_later_config then
+ if mp.get_property_bool("save-position-on-quit") or force_write then
+ mp.command("write-watch-later-config")
+ end
+ end
+end
+
+function playlist_next()
+ write_watch_later(true)
+ mp.commandv("playlist-next", "weak")
+ if settings.close_playlist_on_playfile then
+ remove_keybinds()
+ end
+ refresh_UI()
+end
+
+function playlist_prev()
+ write_watch_later(true)
+ mp.commandv("playlist-prev", "weak")
+ if settings.close_playlist_on_playfile then
+ remove_keybinds()
+ end
+ refresh_UI()
+end
+
+function playlist_random()
+ write_watch_later()
+ refresh_globals()
+ if plen < 2 then
+ return
+ end
+ math.randomseed(os.time())
+ local random = pos
+ while random == pos do
+ random = math.random(0, plen - 1)
+ end
+ mp.set_property("playlist-pos", random)
+ if settings.close_playlist_on_playfile then
+ remove_keybinds()
+ end
+end
+
+function playfile()
+ refresh_globals()
+ if plen == 0 then
+ return
+ end
+ selection = nil
+ local is_idle = mp.get_property_native("idle-active")
+ if cursor ~= pos or is_idle then
+ write_watch_later()
+ mp.set_property("playlist-pos", cursor)
+ else
+ if cursor ~= plen - 1 then
+ cursor = cursor + 1
+ end
+ write_watch_later()
+ mp.commandv("playlist-next", "weak")
+ end
+ if settings.close_playlist_on_playfile then
+ remove_keybinds()
+ elseif playlist_visible then
+ showplaylist()
+ end
+end
+
+function file_filter(filenames)
+ local files = {}
+ for i = 1, #filenames do
+ local file = filenames[i]
+ local ext = file:match("%.([^%.]+)$")
+ if ext and filetype_lookup[ext:lower()] then
+ table.insert(files, file)
+ end
+ end
+ return files
+end
+
+function get_playlist_filenames_set()
+ local filenames = {}
+ for n = 0, plen - 1, 1 do
+ local filename = mp.get_property("playlist/" .. n .. "/filename")
+ local _, file = utils.split_path(filename)
+ filenames[file] = true
+ end
+ return filenames
+end
+
+--Creates a playlist of all files in directory, will keep the order and position
+--For exaple, Folder has 12 files, you open the 5th file and run this, the remaining 7 are added behind the 5th file and prior 4 files before it
+function playlist(force_dir)
+ refresh_globals()
+ if not directory and plen > 0 then
+ return
+ end
+ local hasfile = true
+ if plen == 0 then
+ hasfile = false
+ dir = mp.get_property("working-directory")
+ else
+ dir = directory
+ end
+
+ if dir == "." then
+ dir = ""
+ end
+ if force_dir then
+ dir = force_dir
+ end
+
+ local files = file_filter(utils.readdir(dir, "files"))
+ if winapisort ~= nil then
+ table.sort(files, winapisort)
+ else
+ table.sort(files, alphanumsort)
+ end
+
+ if files == nil then
+ msg.verbose("no files in directory")
+ return
+ end
+
+ local filenames = get_playlist_filenames_set()
+ local c, c2 = 0, 0
+ if files then
+ local cur = false
+ local filename = mp.get_property("filename")
+ for _, file in ipairs(files) do
+ if file == nil or file[1] == "." then
+ break
+ end
+ local appendstr = "append"
+ if not hasfile then
+ cur = true
+ appendstr = "append-play"
+ hasfile = true
+ end
+ if filename == file then
+ cur = true
+ elseif filenames[file] then
+ -- skip files already in playlist
+ elseif cur == true or settings.loadfiles_always_append then
+ mp.commandv("loadfile", utils.join_path(dir, file), appendstr)
+ msg.info("Appended to playlist: " .. file)
+ c2 = c2 + 1
+ else
+ mp.commandv("loadfile", utils.join_path(dir, file), appendstr)
+ msg.info("Prepended to playlist: " .. file)
+ mp.commandv("playlist-move", mp.get_property_number("playlist-count", 1) - 1, c)
+ c = c + 1
+ end
+ end
+ if c2 > 0 or c > 0 then
+ msg.info("Added " .. c + c2 .. " files to playlist")
+ else
+ msg.info("No additional files found")
+ end
+ cursor = mp.get_property_number("playlist-pos", 1)
+ else
+ msg.error("Could not scan for files: " .. (error or ""))
+ end
+ refresh_globals()
+ if playlist_visible then
+ showplaylist()
+ end
+ if settings.display_osd_feedback then
+ if c2 > 0 or c > 0 then
+ mp.osd_message("Added " .. c + c2 .. " files to playlist")
+ else
+ mp.osd_message("No additional files found")
+ end
+ end
+ return c + c2
+end
+
+function parse_home(path)
+ if not path:find("^~") then
+ return path
+ end
+ local home_dir = os.getenv("HOME") or os.getenv("USERPROFILE")
+ if not home_dir then
+ local drive = os.getenv("HOMEDRIVE")
+ local path = os.getenv("HOMEPATH")
+ if drive and path then
+ home_dir = utils.join_path(drive, path)
+ else
+ msg.error("Couldn't find home dir.")
+ return nil
+ end
+ end
+ local result = path:gsub("^~", home_dir)
+ return result
+end
+
+local interactive_save = false
+function activate_playlist_save()
+ if interactive_save then
+ remove_keybinds()
+ mp.command('script-message playlistmanager-save-interactive "start interactive filenaming process"')
+ else
+ save_playlist()
+ end
+end
+
+--saves the current playlist into a m3u file
+function save_playlist(filename)
+ local length = mp.get_property_number("playlist-count", 0)
+ if length == 0 then
+ return
+ end
+
+ --get playlist save path
+ local savepath
+ if settings.playlist_savepath == nil or settings.playlist_savepath == "" then
+ savepath = mp.command_native({ "expand-path", "~~home/" }) .. "/playlists"
+ else
+ savepath = parse_home(settings.playlist_savepath)
+ if savepath == nil then
+ return
+ end
+ end
+
+ --create savepath if it doesn't exist
+ if utils.readdir(savepath) == nil then
+ local windows_args = { "powershell", "-NoProfile", "-Command", "mkdir", savepath }
+ local unix_args = { "mkdir", savepath }
+ local args = settings.system == "windows" and windows_args or unix_args
+ local res = utils.subprocess({ args = args, cancellable = false })
+ if res.status ~= 0 then
+ msg.error(
+ "Failed to create playlist save directory " .. savepath .. ". Error: " .. (res.error or "unknown")
+ )
+ return
+ end
+ end
+
+ local name = filename
+ if name == nil then
+ if settings.playlist_save_filename == nil or settings.playlist_save_filename == "" then
+ local date = os.date("*t")
+ local datestring = ("%02d-%02d-%02d_%02d-%02d-%02d"):format(
+ date.year,
+ date.month,
+ date.day,
+ date.hour,
+ date.min,
+ date.sec
+ )
+
+ name = datestring .. "_playlist-size_" .. length .. ".m3u"
+ else
+ name = settings.playlist_save_filename
+ end
+ end
+
+ local savepath = utils.join_path(savepath, name)
+ local file, err = io.open(savepath, "w")
+ if not file then
+ msg.error("Error in creating playlist file, check permissions. Error: " .. (err or "unknown"))
+ else
+ file:write("#EXTM3U\n")
+ local i = 0
+ while i < length do
+ local pwd = mp.get_property("working-directory")
+ local filename = mp.get_property("playlist/" .. i .. "/filename")
+ local fullpath = filename
+ if not is_protocol(filename) then
+ fullpath = utils.join_path(pwd, filename)
+ end
+ local title = mp.get_property("playlist/" .. i .. "/title") or title_table[filename]
+ if title then
+ file:write("#EXTINF:," .. title .. "\n")
+ end
+ file:write(fullpath, "\n")
+ i = i + 1
+ end
+ local saved_msg = "Playlist written to: " .. savepath
+ if settings.display_osd_feedback then
+ mp.osd_message(saved_msg)
+ end
+ msg.info(saved_msg)
+ file:close()
+ end
+end
+
+function alphanumsort(a, b)
+ local function padnum(d)
+ local dec, n = string.match(d, "(%.?)0*(.+)")
+ return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
+ end
+ return tostring(a):lower():gsub("%.?%d+", padnum) .. ("%3d"):format(#b)
+ < tostring(b):lower():gsub("%.?%d+", padnum) .. ("%3d"):format(#a)
+end
+
+-- fast sort algo from https://github.com/zsugabubus/dotfiles/blob/master/.config/mpv/scripts/playlist-filtersort.lua
+function sortplaylist(startover)
+ local playlist = mp.get_property_native("playlist")
+ if #playlist < 2 then
+ return
+ end
+
+ local order = {}
+ for i = 1, #playlist do
+ order[i] = i
+ playlist[i].string = get_name_from_index(i - 1, true)
+ end
+
+ table.sort(order, function(a, b)
+ return sort_modes[sort_mode].sort_fn(a, b, playlist)
+ end)
+
+ for i = 1, #playlist do
+ playlist[order[i]].new_pos = i
+ end
+
+ for i = 1, #playlist do
+ while true do
+ local j = playlist[i].new_pos
+ if i == j then
+ break
+ end
+ mp.commandv("playlist-move", i - 1, (j + 1) - 1)
+ mp.commandv("playlist-move", (j - 1) - 1, i - 1)
+ playlist[j], playlist[i] = playlist[i], playlist[j]
+ end
+ end
+
+ for i = 1, #playlist do
+ local filename = mp.get_property("playlist/" .. i - 1 .. "/filename")
+ local ext = filename:match("%.([^%.]+)$")
+ if not ext or not filetype_lookup[ext:lower()] then
+ --move the directory to the end of the playlist
+ mp.commandv("playlist-move", i - 1, #playlist)
+ end
+ end
+
+ cursor = mp.get_property_number("playlist-pos", 0)
+ if startover then
+ mp.set_property("playlist-pos", 0)
+ end
+ if playlist_visible then
+ showplaylist()
+ end
+ if settings.display_osd_feedback then
+ mp.osd_message("Playlist sorted with " .. sort_modes[sort_mode].title)
+ end
+end
+
+function reverseplaylist()
+ local length = mp.get_property_number("playlist-count", 0)
+ if length < 2 then
+ return
+ end
+ for outer = 1, length - 1, 1 do
+ mp.commandv("playlist-move", outer, 0)
+ end
+ if playlist_visible then
+ showplaylist()
+ end
+ if settings.display_osd_feedback then
+ mp.osd_message("Playlist reversed")
+ end
+end
+
+function shuffleplaylist()
+ refresh_globals()
+ if plen < 2 then
+ return
+ end
+ mp.command("playlist-shuffle")
+ math.randomseed(os.time())
+ mp.commandv("playlist-move", pos, math.random(0, plen - 1))
+
+ local playlist = mp.get_property_native("playlist")
+ for i = 1, #playlist do
+ local filename = mp.get_property("playlist/" .. i - 1 .. "/filename")
+ local ext = filename:match("%.([^%.]+)$")
+ if not ext or not filetype_lookup[ext:lower()] then
+ --move the directory to the end of the playlist
+ mp.commandv("playlist-move", i - 1, #playlist)
+ end
+ end
+
+ mp.set_property("playlist-pos", 0)
+ refresh_globals()
+ if playlist_visible then
+ showplaylist()
+ end
+ if settings.display_osd_feedback then
+ mp.osd_message("Playlist shuffled")
+ end
+end
+
+function bind_keys(keys, name, func, opts)
+ if keys == nil or keys == "" then
+ mp.add_key_binding(keys, name, func, opts)
+ return
+ end
+ local i = 1
+ for key in keys:gmatch("[^%s]+") do
+ local prefix = i == 1 and "" or i
+ mp.add_key_binding(key, name .. prefix, func, opts)
+ i = i + 1
+ end
+end
+
+function bind_keys_forced(keys, name, func, opts)
+ if keys == nil or keys == "" then
+ mp.add_forced_key_binding(keys, name, func, opts)
+ return
+ end
+ local i = 1
+ for key in keys:gmatch("[^%s]+") do
+ local prefix = i == 1 and "" or i
+ mp.add_forced_key_binding(key, name .. prefix, func, opts)
+ i = i + 1
+ end
+end
+
+function unbind_keys(keys, name)
+ if keys == nil or keys == "" then
+ mp.remove_key_binding(name)
+ return
+ end
+ local i = 1
+ for key in keys:gmatch("[^%s]+") do
+ local prefix = i == 1 and "" or i
+ mp.remove_key_binding(name .. prefix)
+ i = i + 1
+ end
+end
+
+function add_keybinds()
+ bind_keys_forced(settings.key_moveup, "moveup", moveup, "repeatable")
+ bind_keys_forced(settings.key_movedown, "movedown", movedown, "repeatable")
+ bind_keys_forced(settings.key_movepageup, "movepageup", movepageup, "repeatable")
+ bind_keys_forced(settings.key_movepagedown, "movepagedown", movepagedown, "repeatable")
+ bind_keys_forced(settings.key_movebegin, "movebegin", movebegin, "repeatable")
+ bind_keys_forced(settings.key_moveend, "moveend", moveend, "repeatable")
+ bind_keys_forced(settings.key_selectfile, "selectfile", selectfile)
+ bind_keys_forced(settings.key_unselectfile, "unselectfile", unselectfile)
+ bind_keys_forced(settings.key_playfile, "playfile", playfile)
+ bind_keys_forced(settings.key_removefile, "removefile", removefile, "repeatable")
+ bind_keys_forced(settings.key_closeplaylist, "closeplaylist", remove_keybinds)
+end
+
+function remove_keybinds()
+ keybindstimer:kill()
+ keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
+ keybindstimer:kill()
+ playlist_overlay.data = ""
+ playlist_overlay:update()
+ playlist_visible = false
+ if settings.reset_cursor_on_close then
+ resetcursor()
+ end
+ if settings.dynamic_binds then
+ unbind_keys(settings.key_moveup, "moveup")
+ unbind_keys(settings.key_movedown, "movedown")
+ unbind_keys(settings.key_movepageup, "movepageup")
+ unbind_keys(settings.key_movepagedown, "movepagedown")
+ unbind_keys(settings.key_movebegin, "movebegin")
+ unbind_keys(settings.key_moveend, "moveend")
+ unbind_keys(settings.key_selectfile, "selectfile")
+ unbind_keys(settings.key_unselectfile, "unselectfile")
+ unbind_keys(settings.key_playfile, "playfile")
+ unbind_keys(settings.key_removefile, "removefile")
+ unbind_keys(settings.key_closeplaylist, "closeplaylist")
+ end
+end
+
+keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
+keybindstimer:kill()
+
+if not settings.dynamic_binds then
+ add_keybinds()
+end
+
+if settings.loadfiles_on_idle_start and mp.get_property_number("playlist-count", 0) == 0 then
+ playlist()
+end
+
+mp.observe_property("playlist-count", "number", function(_, plcount)
+ --if we promised to listen and sort on playlist size increase do it
+ if settings.sortplaylist_on_file_add and (plcount > plen) then
+ msg.info("Added files will be automatically sorted")
+ refresh_globals()
+ sortplaylist()
+ end
+ refresh_UI()
+ resolve_titles()
+end)
+
+url_request_queue = {}
+function url_request_queue.push(item)
+ table.insert(url_request_queue, item)
+end
+function url_request_queue.pop()
+ return table.remove(url_request_queue, 1)
+end
+local url_titles_to_fetch = url_request_queue
+local ongoing_url_requests = {}
+
+function url_fetching_throttler()
+ if #url_titles_to_fetch == 0 then
+ url_title_fetch_timer:kill()
+ end
+
+ local ongoing_url_requests_count = 0
+ for _, ongoing in pairs(ongoing_url_requests) do
+ if ongoing then
+ ongoing_url_requests_count = ongoing_url_requests_count + 1
+ end
+ end
+
+ -- start resolving some url titles if there is available slots
+ local amount_to_fetch = math.max(0, settings.concurrent_title_resolve_limit - ongoing_url_requests_count)
+ for index = 1, amount_to_fetch, 1 do
+ local file = url_titles_to_fetch.pop()
+ if file then
+ ongoing_url_requests[file] = true
+ resolve_ytdl_title(file)
+ end
+ end
+end
+
+url_title_fetch_timer = mp.add_periodic_timer(0.1, url_fetching_throttler)
+url_title_fetch_timer:kill()
+
+local_request_queue = {}
+function local_request_queue.push(item)
+ table.insert(local_request_queue, item)
+end
+function local_request_queue.pop()
+ return table.remove(local_request_queue, 1)
+end
+local local_titles_to_fetch = local_request_queue
+local ongoing_local_request = false
+
+-- this will only allow 1 concurrent local title resolve process
+function local_fetching_throttler()
+ if not ongoing_local_request then
+ local file = local_titles_to_fetch.pop()
+ if file then
+ ongoing_local_request = true
+ resolve_ffprobe_title(file)
+ end
+ end
+end
+
+function resolve_titles()
+ if settings.prefer_titles == "none" then
+ return
+ end
+ if not settings.resolve_url_titles and not settings.resolve_local_titles then
+ return
+ end
+
+ local length = mp.get_property_number("playlist-count", 0)
+ if length < 2 then
+ return
+ end
+ -- loop all items in playlist because we can't predict how it has changed
+ local added_urls = false
+ local added_local = false
+ for i = 0, length - 1, 1 do
+ local filename = mp.get_property("playlist/" .. i .. "/filename")
+ local title = mp.get_property("playlist/" .. i .. "/title")
+ if i ~= pos and filename and not title and not title_table[filename] and not requested_titles[filename] then
+ requested_titles[filename] = true
+ if filename:match("^https?://") then
+ url_titles_to_fetch.push(filename)
+ added_urls = true
+ elseif settings.prefer_titles == "all" then
+ local_titles_to_fetch.push(filename)
+ added_local = true
+ end
+ end
+ end
+ if added_urls then
+ url_title_fetch_timer:resume()
+ end
+ if added_local then
+ local_fetching_throttler()
+ end
+end
+
+function resolve_ytdl_title(filename)
+ local args = {
+ settings.youtube_dl_executable,
+ "--no-playlist",
+ "--flat-playlist",
+ "-sJ",
+ "--no-config",
+ filename,
+ }
+ local req = mp.command_native_async({
+ name = "subprocess",
+ args = args,
+ playback_only = false,
+ capture_stdout = true,
+ }, function(success, res)
+ ongoing_url_requests[filename] = false
+ if res.killed_by_us then
+ msg.verbose("Request to resolve url title " .. filename .. " timed out")
+ return
+ end
+ if res.status == 0 then
+ local json, err = utils.parse_json(res.stdout)
+ if not err then
+ local is_playlist = json["_type"] and json["_type"] == "playlist"
+ local title = (is_playlist and "[playlist]: " or "") .. json["title"]
+ msg.verbose(filename .. " resolved to '" .. title .. "'")
+ title_table[filename] = title
+ refresh_UI()
+ else
+ msg.error("Failed parsing json, reason: " .. (err or "unknown"))
+ end
+ else
+ msg.error("Failed to resolve url title " .. filename .. " Error: " .. (res.error or "unknown"))
+ end
+ end)
+
+ mp.add_timeout(settings.resolve_title_timeout, function()
+ mp.abort_async_command(req)
+ ongoing_url_requests[filename] = false
+ end)
+end
+
+function resolve_ffprobe_title(filename)
+ local args = { "ffprobe", "-show_format", "-show_entries", "format=tags", "-loglevel", "quiet", filename }
+ local req = mp.command_native_async({
+ name = "subprocess",
+ args = args,
+ playback_only = false,
+ capture_stdout = true,
+ }, function(success, res)
+ ongoing_local_request = false
+ local_fetching_throttler()
+ if res.killed_by_us then
+ msg.verbose("Request to resolve local title " .. filename .. " timed out")
+ return
+ end
+ if res.status == 0 then
+ local title = string.match(res.stdout, "title=([^\n\r]+)")
+ if title then
+ msg.verbose(filename .. " resolved to '" .. title .. "'")
+ title_table[filename] = title
+ refresh_UI()
+ end
+ else
+ msg.error("Failed to resolve local title " .. filename .. " Error: " .. (res.error or "unknown"))
+ end
+ end)
+end
+
+--script message handler
+function handlemessage(msg, value, value2)
+ if msg == "show" and value == "playlist" then
+ if value2 ~= "toggle" then
+ showplaylist(value2)
+ return
+ else
+ toggle_playlist(showplaylist)
+ return
+ end
+ end
+ if msg == "show" and value == "playlist-nokeys" then
+ if value2 ~= "toggle" then
+ showplaylist_non_interactive(value2)
+ return
+ else
+ toggle_playlist(showplaylist_non_interactive)
+ return
+ end
+ end
+ if msg == "show" and value == "filename" and strippedname and value2 then
+ mp.commandv("show-text", strippedname, tonumber(value2) * 1000)
+ return
+ end
+ if msg == "show" and value == "filename" and strippedname then
+ mp.commandv("show-text", strippedname)
+ return
+ end
+ if msg == "sort" then
+ sortplaylist(value)
+ return
+ end
+ if msg == "shuffle" then
+ shuffleplaylist()
+ return
+ end
+ if msg == "reverse" then
+ reverseplaylist()
+ return
+ end
+ if msg == "loadfiles" then
+ playlist(value)
+ return
+ end
+ if msg == "save" then
+ save_playlist(value)
+ return
+ end
+ if msg == "playlist-next" then
+ playlist_next()
+ return
+ end
+ if msg == "playlist-prev" then
+ playlist_prev()
+ return
+ end
+ if msg == "playlist-next-random" then
+ playlist_random()
+ return
+ end
+ if msg == "enable-interactive-save" then
+ interactive_save = true
+ end
+ if msg == "close" then
+ remove_keybinds()
+ end
+end
+
+mp.register_script_message("playlistmanager", handlemessage)
+
+bind_keys(settings.key_sortplaylist, "sortplaylist", function()
+ sortplaylist()
+ sort_mode = sort_mode + 1
+ if sort_mode > #sort_modes then
+ sort_mode = 1
+ end
+end)
+bind_keys(settings.key_shuffleplaylist, "shuffleplaylist", shuffleplaylist)
+bind_keys(settings.key_reverseplaylist, "reverseplaylist", reverseplaylist)
+bind_keys(settings.key_loadfiles, "loadfiles", playlist)
+bind_keys(settings.key_saveplaylist, "saveplaylist", activate_playlist_save)
+bind_keys(settings.key_showplaylist, "showplaylist", showplaylist)
+bind_keys(settings.key_peek_at_playlist, "peek_at_playlist", handle_complex_playlist_toggle, { complex = true })
+
+mp.register_event("start-file", on_start_file)
+mp.register_event("file-loaded", on_file_loaded)
+mp.register_event("end-file", on_end_file)
diff --git a/ar/.config/mpv/scripts/reload.lua b/ar/.config/mpv/scripts/reload.lua
new file mode 100644
index 0000000..dc449ec
--- /dev/null
+++ b/ar/.config/mpv/scripts/reload.lua
@@ -0,0 +1,19 @@
+--[[
+ reload / by sibwaf / https://github.com/sibwaf/mpv-scripts
+
+ Reopens the current playing file and seeks to the same timestamp on a button press ([Shift+R] by default).
+ Useful for situations when you are watching YouTube/streams and your connection breaks for some reason.
+
+ MIT license - do whatever you want, but I'm not responsible for any possible problems.
+ Please keep the URL to the original repository. Thanks!
+]]
+
+function reload()
+ local path = mp.get_property("path")
+ if path ~= nil then
+ local time = mp.get_property_number("time-pos")
+ mp.commandv("loadfile", path, "replace", "start=" .. time)
+ end
+end
+
+mp.add_key_binding("R", "reload", reload)
diff --git a/ar/.config/mpv/scripts/sponsorblock_minimal.lua b/ar/.config/mpv/scripts/sponsorblock_minimal.lua
new file mode 100644
index 0000000..c375480
--- /dev/null
+++ b/ar/.config/mpv/scripts/sponsorblock_minimal.lua
@@ -0,0 +1,141 @@
+-- sponsorblock_minimal.lua
+--
+-- This script skips sponsored segments of YouTube videos
+-- using data from https://github.com/ajayyy/SponsorBlock
+
+local opt = require("mp.options")
+local utils = require("mp.utils")
+
+local ON = false
+local ranges = nil
+
+local options = {
+ server = "https://sponsor.ajay.app/api/skipSegments",
+
+ -- Categories to fetch and skip
+ categories = '"sponsor"',
+
+ -- Set this to "true" to use sha256HashPrefix instead of videoID
+ hash = "",
+}
+
+opt.read_options(options)
+
+function skip_ads(name, pos)
+ if pos then
+ for _, i in pairs(ranges) do
+ v = i.segment[2]
+ if i.segment[1] <= pos and v > pos then
+ --this message may sometimes be wrong
+ --it only seems to be a visual thing though
+ mp.osd_message(
+ ("[sponsorblock] skipping forward %ds"):format(math.floor(v - mp.get_property("time-pos")))
+ )
+ --need to do the +0.01 otherwise mpv will start spamming skip sometimes
+ --example: https://www.youtube.com/watch?v=4ypMJzeNooo
+ mp.set_property("time-pos", v + 0.01)
+ return
+ end
+ end
+ end
+end
+
+function file_loaded()
+ local video_path = mp.get_property("path", "")
+ local video_referer = string.match(mp.get_property("http-header-fields", ""), "Referer:([^,]+)") or ""
+
+ local urls = {
+ "ytdl://youtu%.be/([%w-_]+).*",
+ "ytdl://w?w?w?%.?youtube%.com/v/([%w-_]+).*",
+ "https?://youtu%.be/([%w-_]+).*",
+ "https?://w?w?w?%.?youtube%.com/v/([%w-_]+).*",
+ "/watch.*[?&]v=([%w-_]+).*",
+ "/embed/([%w-_]+).*",
+ "^ytdl://([%w-_]+)$",
+ "-([%w-_]+)%.",
+ }
+ local youtube_id = nil
+ local purl = mp.get_property("metadata/by-key/PURL", "")
+ for i, url in ipairs(urls) do
+ youtube_id = youtube_id
+ or string.match(video_path, url)
+ or string.match(video_referer, url)
+ or string.match(purl, url)
+ if youtube_id then
+ break
+ end
+ end
+
+ if not youtube_id or string.len(youtube_id) < 11 then
+ return
+ end
+ youtube_id = string.sub(youtube_id, 1, 11)
+
+ local args = { "curl", "-L", "-s", "-G", "--data-urlencode", ("categories=[%s]"):format(options.categories) }
+ local url = options.server
+ if options.hash == "true" then
+ local sha = mp.command_native({
+ name = "subprocess",
+ capture_stdout = true,
+ args = { "sha256sum" },
+ stdin_data = youtube_id,
+ })
+ url = ("%s/%s"):format(url, string.sub(sha.stdout, 0, 4))
+ else
+ table.insert(args, "--data-urlencode")
+ table.insert(args, "videoID=" .. youtube_id)
+ end
+ table.insert(args, url)
+
+ local sponsors = mp.command_native({
+ name = "subprocess",
+ capture_stdout = true,
+ playback_only = false,
+ args = args,
+ })
+ if sponsors.stdout then
+ local json = utils.parse_json(sponsors.stdout)
+ if type(json) == "table" then
+ if options.hash == "true" then
+ for _, i in pairs(json) do
+ if i.videoID == youtube_id then
+ ranges = i.segments
+ break
+ end
+ end
+ else
+ ranges = json
+ end
+
+ if ranges then
+ ON = true
+ mp.add_key_binding("", "sponsorblock", toggle)
+ mp.observe_property("time-pos", "native", skip_ads)
+ end
+ end
+ end
+end
+
+function end_file()
+ if not ON then
+ return
+ end
+ mp.unobserve_property(skip_ads)
+ ranges = nil
+ ON = false
+end
+
+function toggle()
+ if ON then
+ mp.unobserve_property(skip_ads)
+ mp.osd_message("[sponsorblock] off")
+ ON = false
+ else
+ mp.observe_property("time-pos", "native", skip_ads)
+ mp.osd_message("[sponsorblock] on")
+ ON = true
+ end
+end
+
+mp.register_event("file-loaded", file_loaded)
+mp.register_event("end-file", end_file)
diff --git a/ar/.config/mpv/scripts/subtitle-search.lua b/ar/.config/mpv/scripts/subtitle-search.lua
new file mode 100644
index 0000000..53a2ddc
--- /dev/null
+++ b/ar/.config/mpv/scripts/subtitle-search.lua
@@ -0,0 +1,682 @@
+--[[
+Based on sub-search script by kelciour (https://github.com/kelciour/mpv-scripts/blob/master/sub-search.lua)
+
+Differences from the original script:
+
+- Searches in a subtitle file active as a primary subtitle instead of attempting to find subtitle files matching video name
+- Outputs all search results in OSD list instead of jumping between them with a hotkey (the closest subtitle is selected by default)
+- Supports searching unicode text (subtitles should be encoded as utf8, please re-encode your subtitles if you get no results searching for unicode text)
+- Embedded console replaced with more recent variant from mpv sources (to support unicode input)
+- Takes into account current `sub-delay` value
+- Can search in embedded subtitles (requires ffmpeg to be installed to extract subtitles from video files)
+- Can search subtitles for youtube videos (requires ffmpeg to be installed to fetch remote subtitles)
+- Supports `.srt`, `.vtt` and `.sub` (microdvd) subtitle formats
+- Can use special phrase "*" to show all subtitle lines
+- Use `ctrl+shift+f` shortcut to show all subtitle lines simultaneously and dynamically highlight the current line
+- Press `Ctrl+Shift+Enter` in result list to adjust `sub-delay` so that selected subtitle line is displayed at the current position
+
+Requires `script-modules/utf8` repository, `script-modules/scroll-list.lua`, `script-modules/sha1.lua`, `script-modules/utf8_data.lua` and `script-modules/input-console.lua` to work.
+
+You can clone `script-modules/utf8` repository with the following command (assuming you are in mpv config directory): `git clone git@github.com:Stepets/utf8.lua.git script-modules/utf8`
+
+Usage:
+ Press Ctrl + F, print something and press Enter.
+Example:
+ 'You are playing Empire Strikes Back and press Ctrl+F, type "I am you father" + Enter
+ and voilá, the scene pops up.'
+--]]
+
+
+package.path = package.path .. ";" .. mp.command_native({ "expand-path", "~~/script-modules/?.lua" })
+
+local mp = require("mp")
+local utils = require("mp.utils")
+local msg = require("mp.msg")
+local input_console = require("input-console")
+local result_list = require("scroll-list")
+local utf8 = require("utf8/init")
+local utf8_data = require("utf8_data")
+local sha1 = require("sha1")
+
+utf8.config = {
+ conversion = {
+ uc_lc = utf8_data.utf8_uc_lc,
+ lc_uc = utf8_data.utf8_lc_uc
+ },
+}
+
+utf8:init()
+
+table.insert(result_list.keybinds, {
+ "ENTER", "jump_to_result", function()
+ local selected_index = result_list.selected
+ if selected_index == nil then
+ return
+ end
+
+ local selected = result_list.list[selected_index]
+ mp.commandv("seek", selected.time, "absolute+exact")
+ end, {}
+})
+table.insert(result_list.keybinds, {
+ "Ctrl+Shift+ENTER", "sync_to_result", function()
+ local selected_index = result_list.selected
+ if selected_index == nil then
+ return
+ end
+
+ local selected = result_list.list[selected_index]
+ local old_delay = mp.get_property_native("sub-delay")
+ local delay = -(selected.original_time - mp.get_property_native("time-pos"))
+ mp.set_property_native("sub-delay", delay)
+ end, {}
+})
+
+function sub_time_to_seconds(time, sep)
+ if time:match("%d%d:%d%d" .. sep .. "%d%d%d") then
+ time = "00:" .. time
+ end
+
+ local major, minor = time:match("(%d%d:%d%d:%d%d)" .. sep .. "(%d%d%d)")
+ local hours, mins, secs = major:match("(%d%d):(%d%d):(%d%d)")
+ return hours * 3600 + mins * 60 + secs + minor / 1000
+end
+
+local subs_cache = {}
+
+function open_file(path)
+ local f, err = io.open(path, "r")
+ if f and err == nil then
+ return f
+ end
+
+ return nil
+end
+
+function is_supported_network_protocol(url)
+ local protocols = { "http", "https" }
+
+ for _, protocol in pairs(protocols) do
+ if url:sub(1, #protocol + 3) == protocol .. "://" then
+ return true
+ end
+ end
+
+ return false
+end
+
+function get_sub_filename_async(track_name, on_done)
+ local active_track = mp.get_property_native("current-tracks/" .. track_name)
+ if active_track == nil then
+ on_done(nil)
+ return
+ end
+
+ local is_external = active_track.external
+ local external_filename = active_track["external-filename"]
+
+ -- youtube subtitles specified with edl format
+ if is_external and external_filename and external_filename:sub(1, 6) == "edl://" then
+ download_subtitle_async(external_filename:match("https://.*"), on_done)
+ return
+ end
+
+ if is_external and external_filename and is_supported_network_protocol(external_filename) then
+ download_subtitle_async(external_filename, on_done)
+ return
+ end
+
+ if is_external and external_filename then
+ on_done(external_filename)
+ return
+ end
+
+ if is_external == false then
+ extract_subtitle_track_async(active_track, on_done)
+ return
+ end
+
+ on_done(nil)
+end
+
+function get_path_to_extract_sub(uniq_sub_id)
+ local sub_filename = sha1.hex(uniq_sub_id)
+ return utils.join_path(get_temp_dir(), "mpv-subtitle-search-extracted-" .. sub_filename .. ".srt")
+end
+
+function download_subtitle_async(url, on_done)
+ local sub_path = get_path_to_extract_sub(mp.get_property_native("path") .. "#" .. url)
+
+ if subs_cache[sub_path] then
+ on_done(sub_path)
+ return
+ end
+
+ local extract_overlay = mp.create_osd_overlay("ass-events")
+ extract_overlay.data = "{\\a3\\fs20}Fetching remote subtitles, wait..."
+ extract_overlay:update()
+
+ mp.command_native_async({
+ name = "subprocess",
+ capture_stdout = true,
+ args = { "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", url, "-vn", "-an", "-c:s", "srt", sub_path }
+ }, function(ok)
+ if not ok then
+ extract_overlay.data = "{\\a3\\fs20\\c&HFF&}Extraction failed"
+ extract_overlay:update()
+
+ mp.add_timeout(2, function()
+ extract_overlay:remove()
+ end)
+
+ on_done(nil)
+ else
+ extract_overlay:remove()
+
+ on_done(sub_path)
+ end
+ end)
+end
+
+function extract_subtitle_track_async(track, on_done)
+ if track.external then
+ on_done(nil)
+ return
+ end
+
+ local video_file = mp.get_property_native("path")
+ local working_dir = mp.get_property_native("working-directory")
+ local full_path = utils.join_path(working_dir, video_file)
+
+ local track_index = track["ff-index"]
+ local sub_path = get_path_to_extract_sub(full_path .. "#" .. track_index)
+
+ -- check if file already exists
+ if open_file(sub_path) then
+ msg.info("Reusing extracted subtitle track from " .. sub_path)
+
+ on_done(sub_path)
+ return
+ end
+
+ msg.info("Extracting embedded subtitle track to " .. sub_path)
+
+ local extract_overlay = mp.create_osd_overlay("ass-events")
+ extract_overlay.data = "{\\a3\\fs20}Extracting embedded subtitles, wait..."
+ extract_overlay:update()
+
+ mp.command_native_async({
+ name = "subprocess",
+ capture_stdout = true,
+ args = { "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", full_path, "-map", "0:" .. track_index, "-vn", "-an", "-c:s", "srt", sub_path }
+ }, function(ok)
+ if not ok then
+ extract_overlay.data = "{\\a3\\fs20\\c&HFF&}Extraction failed"
+ extract_overlay:update()
+
+ mp.add_timeout(2, function()
+ extract_overlay:remove()
+ end)
+
+ on_done(nil)
+ else
+ extract_overlay:remove()
+
+ on_done(sub_path)
+ end
+ end)
+end
+
+function get_temp_dir()
+ local temp_dir = os.getenv("TMPDIR")
+ if temp_dir == nil then
+ temp_dir = os.getenv("TEMP")
+ end
+
+ if temp_dir == nil then
+ temp_dir = os.getenv("TMP")
+ end
+
+ if temp_dir == nil then
+ temp_dir = "/tmp"
+ end
+
+ return temp_dir
+end
+
+function get_lines(input)
+ local lines = {}
+
+ local tail = 1
+ for head = 1, #input do
+ local ch = input:sub(head, head)
+ if ch == "\n" then
+ table.insert(lines, input:sub(tail, head - 1))
+ tail = head + 1
+ elseif head == #input then
+ table.insert(lines, input:sub(tail, head))
+ end
+ end
+
+ return lines
+end
+
+function trim(s)
+ return s:gsub("^%s*(.-)%s*$", "%1")
+end
+
+function parse_vtt_sub(data)
+ local result = {}
+ local state = "header"
+
+ local cur_line = {}
+ for _, line in ipairs(get_lines(data)) do
+ line = trim(line)
+ if state == "header" then
+ if line == "" then
+ state = "body"
+ end
+ elseif state == "body" then
+ if line == "" then
+ state = "header"
+ elseif line:match("^NOTE") or line:match("^STYLE") then
+ state = "comment"
+ else
+ local time_text = line:match("^(%d%d:%d%d:%d%d%.%d%d%d)") or line:match("^(%d%d:%d%d%.%d%d%d)")
+ if time_text then
+ cur_line.time = sub_time_to_seconds(time_text, ".")
+ state = "waiting_text"
+ else
+ state = "body"
+ end
+ end
+ elseif state == "comment" then
+ if #line == 0 then
+ state = "body"
+ end
+ elseif state == "waiting_text" then
+ if #line == 0 or line == nil then
+ if cur_line.text ~= nil then
+ table.insert(result, cur_line)
+ end
+
+ cur_line = {}
+ state = "body"
+ else
+ line = remove_tags(line)
+ if cur_line.text then
+ cur_line.text = cur_line.text .. "\n" .. line
+ else
+ cur_line.text = line
+ end
+ end
+ end
+ end
+
+ return result
+end
+
+function remove_tags(text)
+ function remove_tag(tag_to_remove)
+ return string.gsub(text, "</?" .. tag_to_remove .. ">", "")
+ end
+
+ text = remove_tag("b")
+ text = remove_tag("i")
+ text = remove_tag("u")
+ text = remove_tag("ruby")
+ text = remove_tag("rt")
+
+ -- remove class tag
+ text = remove_tag("c")
+ text = string.gsub(text, "<c.[^>]*>", "")
+
+ -- remove voice tag
+ text = remove_tag("v")
+ text = string.gsub(text, "<v [^>]*>", "")
+
+ -- remove karaoke karaoke tags
+ text = string.gsub(text, "</?%d%d:%d%d.%d%d%d>", "")
+ text = string.gsub(text, "</?%d%d:%d%d:%d%d.%d%d%d>", "")
+
+ -- remove font tag
+ text = string.gsub(text, '<font color="#?[%d%a]+">', "")
+ text = string.gsub(text, '</font>', "")
+
+ return text
+end
+
+
+-- detects only most common encodings
+function get_encoding_from_bom(data)
+ -- utf8
+ local bom = data:sub(1, 3)
+ if bom == "\xEF\xBB\xBF" then
+ return "utf-8"
+ end
+
+ -- utf16
+ bom = data:sub(1, 2)
+ if bom == "\xFF\xFE" or bom == "\xFE\xFF" then
+ return "utf-16"
+ end
+
+ -- utf32
+ bom = data:sub(1, 4)
+ if bom == "\xFF\xFE\x00\x00" or bom == "\x00\x00\xFE\xFF" then
+ return "utf-32"
+ end
+
+ return nil
+end
+
+function is_microdvd_sub(data)
+ return data:match("{%d+}{%d+}")
+end
+
+function parse_microdvd_sub(data)
+ local result = {}
+ local lines = get_lines(data)
+
+ -- if the first line contains only number, it's a subtitle fps
+ local subtitle_fps = tonumber(lines[1])
+ if subtitle_fps == nil or subtitle_fps == 0 then
+ subtitle_fps = mp.get_property_native("container-fps")
+ if subtitle_fps == nil or subtitle_fps == 0 then
+ subtitle_fps = 24
+ end
+ end
+
+ msg.info("Using " .. subtitle_fps .. "fps for microdvd subtitle")
+
+ for _, line in ipairs(lines) do
+ local time_text = line:match("^{(%d+)}{(%d+)}")
+ if time_text then
+ local start_frame = tonumber(time_text:match("^(%d+)"))
+
+ local text = line:match("^{%d+}{%d+}(.*)")
+ text = text:gsub("|", " ")
+ if text then
+ table.insert(result, {
+ time = frame_to_secs(start_frame, subtitle_fps),
+ text = text
+ })
+ end
+ end
+ end
+
+ return result
+end
+
+function frame_to_secs(frame, subtitle_fps)
+ return frame / subtitle_fps
+end
+
+function parse_sub(data)
+ bom_encoding = get_encoding_from_bom(data)
+ if bom_encoding ~= nil then
+ if bom_encoding == "utf-8" then
+ data = data:sub(3)
+ else
+ local error_overlay = mp.create_osd_overlay("ass-events")
+ error_overlay.data = "{\\a3\\fs20\\c&HFF&}Unsupported subtitle encoding: " .. bom_encoding .. ", please re-encode subtitle file to utf-8 to search"
+ error_overlay:update()
+
+ msg.error("Unsupported subtitle encoding: " .. bom_encoding .. ", please re-encode subtitle file to utf-8 to search")
+
+ mp.add_timeout(10, function()
+ error_overlay:remove()
+ end)
+
+ return {}
+ end
+ end
+
+ data = string.gsub(data, "\r\n", "\n")
+
+ if data:sub(1, 6) == "WEBVTT" then
+ return parse_vtt_sub(data)
+ end
+
+ if is_microdvd_sub(data) then
+ return parse_microdvd_sub(data)
+ end
+
+ local result = {}
+ local state = "waiting_index"
+ local cur_line = {}
+ for _, line in ipairs(get_lines(data)) do
+ line = trim(line)
+ if state == "waiting_index" then
+ if cur_line.text then
+ table.insert(result, cur_line)
+ cur_line = {}
+ end
+
+ if line:match("^%d+$") then
+ state = "waiting_time"
+ end
+ elseif state == "waiting_time" then
+ local time_text = line:match("^(%d%d:%d%d:%d%d,%d%d%d) ")
+ if time_text then
+ cur_line.time = sub_time_to_seconds(time_text, ",")
+ state = "waiting_text"
+ else
+ state = "waiting_index"
+ end
+ elseif state == "waiting_text" then
+ line = remove_tags(line)
+ if #line == 0 then
+ if cur_line.text then
+ table.insert(result, cur_line)
+ end
+ cur_line = {}
+ state = "waiting_index"
+ elseif cur_line.text then
+ cur_line.text = cur_line.text .. " " .. line
+ else
+ cur_line.text = line
+ end
+ end
+ end
+
+ if cur_line.text then
+ table.insert(result, cur_line)
+ end
+
+ return result
+end
+
+function load_sub(path, prefix)
+ if not path then
+ return nil
+ end
+
+ local cached = subs_cache[path]
+ if cached then
+ return cached
+ end
+
+ local f = open_file(path)
+ if not f then
+ return nil
+ end
+
+ local data = f:read("*all")
+ f:close()
+
+ local sub = {
+ prefix = prefix,
+ lines = parse_sub(data)
+ }
+ subs_cache[path] = sub
+ return sub
+end
+
+function make_nocase_pattern(s)
+ local result = ""
+ for _, code in utf8.codes(s) do
+ local c = utf8.char(code)
+ result = result .. string.format("[%s%s]", utf8.lower(c), utf8.upper(c))
+ end
+ return result
+end
+
+-- highlight found text with colored text in ass syntax
+function highlight_match(text, match_text, style_reset)
+ local match_start, match_end = utf8.find(utf8.lower(text), utf8.lower(match_text))
+ if match_start == nil then
+ return text
+ end
+
+ local before = result_list.ass_escape(utf8.sub(text, 1, match_start - 1))
+ local match = result_list.ass_escape(utf8.sub(text, match_start, match_end))
+ local after = result_list.ass_escape(utf8.sub(text, match_end + 1))
+
+ if style_reset == "" then
+ style_reset = "{\\c&HFFFFFF&}"
+ end
+
+ return before .. "{\\c&HFF00&}" .. match .. style_reset .. after
+end
+
+function adjust_sub_time(time)
+ local delay = mp.get_property_native("sub-delay")
+ if delay == nil then
+ return time
+ end
+ return time + delay
+end
+
+function divmod (a, b)
+ return math.floor(a / b), a % b
+end
+
+function format_time(time)
+ decimals = 3
+ sep = "."
+ local s = time
+ local h, s = divmod(s, 60 * 60)
+ local m, s = divmod(s, 60)
+
+ local second_format = string.format("%%0%d.%df", 2 + (decimals > 0 and decimals + 1 or 0), decimals)
+
+ return string.format("%02d" .. sep .. "%02d" .. sep .. second_format, h, m, s)
+end
+
+function get_subs_to_search_in_async(on_done)
+ local result = {}
+
+ get_sub_filename_async("sub", function(primary_filename)
+ local sub = load_sub(primary_filename, "P")
+ if sub then
+ table.insert(result, sub)
+ end
+
+ get_sub_filename_async("sub2", function(secondary_filename)
+ sub = load_sub(secondary_filename, "S")
+ if sub then
+ table.insert(result, sub)
+ end
+
+ on_done(result)
+ end)
+ end)
+end
+
+function update_search_results_async(query, live)
+ get_subs_to_search_in_async(function(subs)
+ if #subs == 0 then
+ mp.osd_message("External subtitles not found")
+ return
+ end
+
+ result_list.list = {
+ {
+ sub = nil,
+ time = mp.get_property_native("time-pos"),
+ ass = "Original position"
+ }
+ }
+ result_list.selected = 1
+ result_list.live = live
+
+ local closest_lower_index = 1
+ local closest_lower_time = nil
+ local cur_time = mp.get_property_native("time-pos")
+
+ local pat = "(" .. make_nocase_pattern(query) .. ")"
+ for _, sub in ipairs(subs) do
+ for _, sub_line in ipairs(sub.lines) do
+ if query == "*" or utf8.match(sub_line.text, pat) then
+ local sub_time = adjust_sub_time(sub_line.time)
+
+ table.insert(result_list.list, {
+ sub = sub,
+ original_time = sub_line.time,
+ time = sub_time + 0.01, -- to ensure that the subtitle is visible
+ formatter = function(style_reset)
+ local sub_text = result_list.ass_escape(format_time(sub_time) .. ": ") ..
+ highlight_match(sub_line.text, query, style_reset)
+
+ if #subs > 1 then
+ sub_text = "[" .. sub.prefix .. "] " .. sub_text
+ end
+
+ return sub_text
+ end
+ })
+
+ if sub_time <= cur_time and (closest_lower_time == nil or closest_lower_time < sub_time) then
+ closest_lower_time = sub_time
+ closest_lower_index = #result_list.list
+ end
+ end
+ end
+ end
+
+ result_list.selected = closest_lower_index
+ result_list.header = "Search results for \"" .. query .. "\"\\N ------------------------------------"
+ result_list.header = result_list.header .. "\\NENTER to jump to subtitle, Ctrl+Shift+Enter to adjust subtitle timing to selected line"
+
+ result_list:update()
+ result_list:open()
+ end)
+end
+
+mp.register_script_message('start-search', function()
+ if input_console.is_repl_active() then
+ input_console.set_active(false)
+ else
+ input_console.set_enter_handler(function(query)
+ update_search_results_async(query, false)
+ end)
+ input_console.set_active(true)
+ end
+end)
+
+mp.register_script_message('show-all-lines', function()
+ update_search_results_async("*", true)
+end)
+
+local function get_current_subtitle_index(list, pos)
+ local closest_lower_index = 1
+ local closest_lower_time = nil
+ for i, item in ipairs(list) do
+ if item.time <= pos and (closest_lower_time == nil or closest_lower_time < item.time) then
+ closest_lower_time = item.time
+ closest_lower_index = i
+ end
+ end
+ return closest_lower_index
+end
+
+mp.observe_property("time-pos", "native", function(_, pos)
+ if not result_list.hidden and result_list.live and pos ~= nil then
+ local index = get_current_subtitle_index(result_list.list, pos)
+ if index > 1 then
+ result_list.selected = index
+ result_list:update()
+ end
+ end
+end)
diff --git a/ar/.config/mpv/scripts/thumbfast.lua b/ar/.config/mpv/scripts/thumbfast.lua
new file mode 100644
index 0000000..58d1870
--- /dev/null
+++ b/ar/.config/mpv/scripts/thumbfast.lua
@@ -0,0 +1,951 @@
+-- thumbfast.lua
+--
+-- High-performance on-the-fly thumbnailer
+--
+-- Built for easy integration in third-party UIs.
+
+--[[
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at https://mozilla.org/MPL/2.0/.
+]]
+
+local options = {
+ -- Socket path (leave empty for auto)
+ socket = "",
+
+ -- Thumbnail path (leave empty for auto)
+ thumbnail = "",
+
+ -- Maximum thumbnail generation size in pixels (scaled down to fit)
+ -- Values are scaled when hidpi is enabled
+ max_height = 200,
+ max_width = 200,
+
+ -- Scale factor for thumbnail display size (requires mpv 0.38+)
+ -- Note that this is lower quality than increasing max_height and max_width
+ scale_factor = 1,
+
+ -- Apply tone-mapping, no to disable
+ tone_mapping = "auto",
+
+ -- Overlay id
+ overlay_id = 42,
+
+ -- Spawn thumbnailer on file load for faster initial thumbnails
+ spawn_first = false,
+
+ -- Close thumbnailer process after an inactivity period in seconds, 0 to disable
+ quit_after_inactivity = 0,
+
+ -- Enable on network playback
+ network = false,
+
+ -- Enable on audio playback
+ audio = false,
+
+ -- Enable hardware decoding
+ hwdec = false,
+
+ -- Windows only: use native Windows API to write to pipe (requires LuaJIT)
+ direct_io = false,
+
+ -- Custom path to the mpv executable
+ mpv_path = "mpv"
+}
+
+mp.utils = require "mp.utils"
+mp.options = require "mp.options"
+mp.options.read_options(options, "thumbfast")
+
+local properties = {}
+local pre_0_30_0 = mp.command_native_async == nil
+local pre_0_33_0 = true
+local support_media_control = mp.get_property_native("media-controls") ~= nil
+
+function subprocess(args, async, callback)
+ callback = callback or function() end
+
+ if not pre_0_30_0 then
+ if async then
+ return mp.command_native_async({name = "subprocess", playback_only = true, args = args}, callback)
+ else
+ return mp.command_native({name = "subprocess", playback_only = false, capture_stdout = true, args = args})
+ end
+ else
+ if async then
+ return mp.utils.subprocess_detached({args = args}, callback)
+ else
+ return mp.utils.subprocess({args = args})
+ end
+ end
+end
+
+local winapi = {}
+if options.direct_io then
+ local ffi_loaded, ffi = pcall(require, "ffi")
+ if ffi_loaded then
+ winapi = {
+ ffi = ffi,
+ C = ffi.C,
+ bit = require("bit"),
+ socket_wc = "",
+
+ -- WinAPI constants
+ CP_UTF8 = 65001,
+ GENERIC_WRITE = 0x40000000,
+ OPEN_EXISTING = 3,
+ FILE_FLAG_WRITE_THROUGH = 0x80000000,
+ FILE_FLAG_NO_BUFFERING = 0x20000000,
+ PIPE_NOWAIT = ffi.new("unsigned long[1]", 0x00000001),
+
+ INVALID_HANDLE_VALUE = ffi.cast("void*", -1),
+
+ -- don't care about how many bytes WriteFile wrote, so allocate something to store the result once
+ _lpNumberOfBytesWritten = ffi.new("unsigned long[1]"),
+ }
+ -- cache flags used in run() to avoid bor() call
+ winapi._createfile_pipe_flags = winapi.bit.bor(winapi.FILE_FLAG_WRITE_THROUGH, winapi.FILE_FLAG_NO_BUFFERING)
+
+ ffi.cdef[[
+ void* __stdcall CreateFileW(const wchar_t *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, void *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile);
+ bool __stdcall WriteFile(void *hFile, const void *lpBuffer, unsigned long nNumberOfBytesToWrite, unsigned long *lpNumberOfBytesWritten, void *lpOverlapped);
+ bool __stdcall CloseHandle(void *hObject);
+ bool __stdcall SetNamedPipeHandleState(void *hNamedPipe, unsigned long *lpMode, unsigned long *lpMaxCollectionCount, unsigned long *lpCollectDataTimeout);
+ int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
+ ]]
+
+ winapi.MultiByteToWideChar = function(MultiByteStr)
+ if MultiByteStr then
+ local utf16_len = winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, nil, 0)
+ if utf16_len > 0 then
+ local utf16_str = winapi.ffi.new("wchar_t[?]", utf16_len)
+ if winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then
+ return utf16_str
+ end
+ end
+ end
+ return ""
+ end
+
+ else
+ options.direct_io = false
+ end
+end
+
+local file
+local file_bytes = 0
+local spawned = false
+local disabled = false
+local force_disabled = false
+local spawn_waiting = false
+local spawn_working = false
+local script_written = false
+
+local dirty = false
+
+local x, y
+local last_x, last_y
+
+local last_seek_time
+
+local effective_w, effective_h = options.max_width, options.max_height
+local real_w, real_h
+local last_real_w, last_real_h
+
+local script_name
+
+local show_thumbnail = false
+
+local filters_reset = {["lavfi-crop"]=true, ["crop"]=true}
+local filters_runtime = {["hflip"]=true, ["vflip"]=true}
+local filters_all = {["hflip"]=true, ["vflip"]=true, ["lavfi-crop"]=true, ["crop"]=true}
+
+local tone_mappings = {["none"]=true, ["clip"]=true, ["linear"]=true, ["gamma"]=true, ["reinhard"]=true, ["hable"]=true, ["mobius"]=true}
+local last_tone_mapping
+
+local last_vf_reset = ""
+local last_vf_runtime = ""
+
+local last_rotate = 0
+
+local par = ""
+local last_par = ""
+
+local last_crop = nil
+
+local last_has_vid = 0
+local has_vid = 0
+
+local file_timer
+local file_check_period = 1/60
+
+local allow_fast_seek = true
+
+local client_script = [=[
+#!/usr/bin/env bash
+MPV_IPC_FD=0; MPV_IPC_PATH="%s"
+trap "kill 0" EXIT
+while [[ $# -ne 0 ]]; do case $1 in --mpv-ipc-fd=*) MPV_IPC_FD=${1/--mpv-ipc-fd=/} ;; esac; shift; done
+if echo "print-text thumbfast" >&"$MPV_IPC_FD"; then echo -n > "$MPV_IPC_PATH"; tail -f "$MPV_IPC_PATH" >&"$MPV_IPC_FD" & while read -r -u "$MPV_IPC_FD" 2>/dev/null; do :; done; fi
+]=]
+
+local function get_os()
+ local raw_os_name = ""
+
+ if jit and jit.os and jit.arch then
+ raw_os_name = jit.os
+ else
+ if package.config:sub(1,1) == "\\" then
+ -- Windows
+ local env_OS = os.getenv("OS")
+ if env_OS then
+ raw_os_name = env_OS
+ end
+ else
+ raw_os_name = subprocess({"uname", "-s"}).stdout
+ end
+ end
+
+ raw_os_name = (raw_os_name):lower()
+
+ local os_patterns = {
+ ["windows"] = "windows",
+ ["linux"] = "linux",
+
+ ["osx"] = "darwin",
+ ["mac"] = "darwin",
+ ["darwin"] = "darwin",
+
+ ["^mingw"] = "windows",
+ ["^cygwin"] = "windows",
+
+ ["bsd$"] = "darwin",
+ ["sunos"] = "darwin"
+ }
+
+ -- Default to linux
+ local str_os_name = "linux"
+
+ for pattern, name in pairs(os_patterns) do
+ if raw_os_name:match(pattern) then
+ str_os_name = name
+ break
+ end
+ end
+
+ return str_os_name
+end
+
+local os_name = mp.get_property("platform") or get_os()
+
+local path_separator = os_name == "windows" and "\\" or "/"
+
+if options.socket == "" then
+ if os_name == "windows" then
+ options.socket = "thumbfast"
+ else
+ options.socket = "/tmp/thumbfast"
+ end
+end
+
+if options.thumbnail == "" then
+ if os_name == "windows" then
+ options.thumbnail = os.getenv("TEMP").."\\thumbfast.out"
+ else
+ options.thumbnail = "/tmp/thumbfast.out"
+ end
+end
+
+local unique = mp.utils.getpid()
+
+options.socket = options.socket .. unique
+options.thumbnail = options.thumbnail .. unique
+
+if options.direct_io then
+ if os_name == "windows" then
+ winapi.socket_wc = winapi.MultiByteToWideChar("\\\\.\\pipe\\" .. options.socket)
+ end
+
+ if winapi.socket_wc == "" then
+ options.direct_io = false
+ end
+end
+
+options.scale_factor = math.floor(options.scale_factor)
+
+local mpv_path = options.mpv_path
+local frontend_path
+
+if mpv_path == "mpv" and os_name == "windows" then
+ frontend_path = mp.get_property_native("user-data/frontend/process-path")
+ mpv_path = frontend_path or mpv_path
+end
+
+if mpv_path == "mpv" and os_name == "darwin" and unique then
+ -- TODO: look into ~~osxbundle/
+ mpv_path = string.gsub(subprocess({"ps", "-o", "comm=", "-p", tostring(unique)}).stdout, "[\n\r]", "")
+ if mpv_path ~= "mpv" then
+ mpv_path = string.gsub(mpv_path, "/mpv%-bundle$", "/mpv")
+ local mpv_bin = mp.utils.file_info("/usr/local/mpv")
+ if mpv_bin and mpv_bin.is_file then
+ mpv_path = "/usr/local/mpv"
+ else
+ local mpv_app = mp.utils.file_info("/Applications/mpv.app/Contents/MacOS/mpv")
+ if mpv_app and mpv_app.is_file then
+ mp.msg.warn("symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`")
+ else
+ mp.msg.warn("drag to your Applications folder and symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`")
+ end
+ end
+ end
+end
+
+local function vo_tone_mapping()
+ local passes = mp.get_property_native("vo-passes")
+ if passes and passes["fresh"] then
+ for k, v in pairs(passes["fresh"]) do
+ for k2, v2 in pairs(v) do
+ if k2 == "desc" and v2 then
+ local tone_mapping = string.match(v2, "([0-9a-z.-]+) tone map")
+ if tone_mapping then
+ return tone_mapping
+ end
+ end
+ end
+ end
+ end
+end
+
+local function vf_string(filters, full)
+ local vf = ""
+ local vf_table = properties["vf"]
+
+ if (properties["video-crop"] or "") ~= "" then
+ vf = "lavfi-crop="..string.gsub(properties["video-crop"], "(%d*)x?(%d*)%+(%d+)%+(%d+)", "w=%1:h=%2:x=%3:y=%4")..","
+ local width = properties["video-out-params"] and properties["video-out-params"]["dw"]
+ local height = properties["video-out-params"] and properties["video-out-params"]["dh"]
+ if width and height then
+ vf = string.gsub(vf, "w=:h=:", "w="..width..":h="..height..":")
+ end
+ end
+
+ if vf_table and #vf_table > 0 then
+ for i = #vf_table, 1, -1 do
+ if filters[vf_table[i].name] then
+ local args = ""
+ for key, value in pairs(vf_table[i].params) do
+ if args ~= "" then
+ args = args .. ":"
+ end
+ args = args .. key .. "=" .. value
+ end
+ vf = vf .. vf_table[i].name .. "=" .. args .. ","
+ end
+ end
+ end
+
+ if (full and options.tone_mapping ~= "no") or options.tone_mapping == "auto" then
+ if properties["video-params"] and properties["video-params"]["primaries"] == "bt.2020" then
+ local tone_mapping = options.tone_mapping
+ if tone_mapping == "auto" then
+ tone_mapping = last_tone_mapping or properties["tone-mapping"]
+ if tone_mapping == "auto" and properties["current-vo"] == "gpu-next" then
+ tone_mapping = vo_tone_mapping()
+ end
+ end
+ if not tone_mappings[tone_mapping] then
+ tone_mapping = "hable"
+ end
+ last_tone_mapping = tone_mapping
+ vf = vf .. "zscale=transfer=linear,format=gbrpf32le,tonemap="..tone_mapping..",zscale=transfer=bt709,"
+ end
+ end
+
+ if full then
+ vf = vf.."scale=w="..effective_w..":h="..effective_h..par..",pad=w="..effective_w..":h="..effective_h..":x=-1:y=-1,format=bgra"
+ end
+
+ return vf
+end
+
+local function calc_dimensions()
+ local width = properties["video-out-params"] and properties["video-out-params"]["dw"]
+ local height = properties["video-out-params"] and properties["video-out-params"]["dh"]
+ if not width or not height then return end
+
+ local scale = properties["display-hidpi-scale"] or 1
+
+ if width / height > options.max_width / options.max_height then
+ effective_w = math.floor(options.max_width * scale + 0.5)
+ effective_h = math.floor(height / width * effective_w + 0.5)
+ else
+ effective_h = math.floor(options.max_height * scale + 0.5)
+ effective_w = math.floor(width / height * effective_h + 0.5)
+ end
+
+ local v_par = properties["video-out-params"] and properties["video-out-params"]["par"] or 1
+ if v_par == 1 then
+ par = ":force_original_aspect_ratio=decrease"
+ else
+ par = ""
+ end
+end
+
+local info_timer = nil
+
+local function info(w, h)
+ local rotate = properties["video-params"] and properties["video-params"]["rotate"]
+ local image = properties["current-tracks/video"] and properties["current-tracks/video"]["image"]
+ local albumart = image and properties["current-tracks/video"]["albumart"]
+
+ disabled = (w or 0) == 0 or (h or 0) == 0 or
+ has_vid == 0 or
+ (properties["demuxer-via-network"] and not options.network) or
+ (albumart and not options.audio) or
+ (image and not albumart) or
+ force_disabled
+
+ if info_timer then
+ info_timer:kill()
+ info_timer = nil
+ elseif has_vid == 0 or (rotate == nil and not disabled) then
+ info_timer = mp.add_timeout(0.05, function() info(w, h) end)
+ end
+
+ local json, err = mp.utils.format_json({width=w * options.scale_factor, height=h * options.scale_factor, scale_factor=options.scale_factor, disabled=disabled, available=true, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id})
+ if pre_0_30_0 then
+ mp.command_native({"script-message", "thumbfast-info", json})
+ else
+ mp.command_native_async({"script-message", "thumbfast-info", json}, function() end)
+ end
+end
+
+local function remove_thumbnail_files()
+ if file then
+ file:close()
+ file = nil
+ file_bytes = 0
+ end
+ os.remove(options.thumbnail)
+ os.remove(options.thumbnail..".bgra")
+end
+
+local activity_timer
+
+local function spawn(time)
+ if disabled then return end
+
+ local path = properties["path"]
+ if path == nil then return end
+
+ if options.quit_after_inactivity > 0 then
+ if show_thumbnail or activity_timer:is_enabled() then
+ activity_timer:kill()
+ end
+ activity_timer:resume()
+ end
+
+ local open_filename = properties["stream-open-filename"]
+ local ytdl = open_filename and properties["demuxer-via-network"] and path ~= open_filename
+ if ytdl then
+ path = open_filename
+ end
+
+ remove_thumbnail_files()
+
+ local vid = properties["vid"]
+ has_vid = vid or 0
+
+ local args = {
+ mpv_path, "--no-config", "--msg-level=all=no", "--idle", "--pause", "--keep-open=always", "--really-quiet", "--no-terminal",
+ "--load-scripts=no", "--osc=no", "--ytdl=no", "--load-stats-overlay=no", "--load-osd-console=no", "--load-auto-profiles=no",
+ "--edition="..(properties["edition"] or "auto"), "--vid="..(vid or "auto"), "--no-sub", "--no-audio",
+ "--start="..time, allow_fast_seek and "--hr-seek=no" or "--hr-seek=yes",
+ "--ytdl-format=worst", "--demuxer-readahead-secs=0", "--demuxer-max-bytes=128KiB",
+ "--vd-lavc-skiploopfilter=all", "--vd-lavc-software-fallback=1", "--vd-lavc-fast", "--vd-lavc-threads=2", "--hwdec="..(options.hwdec and "auto" or "no"),
+ "--vf="..vf_string(filters_all, true),
+ "--sws-scaler=fast-bilinear",
+ "--video-rotate="..last_rotate,
+ "--ovc=rawvideo", "--of=image2", "--ofopts=update=1", "--o="..options.thumbnail
+ }
+
+ if not pre_0_30_0 then
+ table.insert(args, "--sws-allow-zimg=no")
+ end
+
+ if support_media_control then
+ table.insert(args, "--media-controls=no")
+ end
+
+ if os_name == "darwin" and properties["macos-app-activation-policy"] then
+ table.insert(args, "--macos-app-activation-policy=accessory")
+ end
+
+ if os_name == "windows" or pre_0_33_0 then
+ table.insert(args, "--input-ipc-server="..options.socket)
+ elseif not script_written then
+ local client_script_path = options.socket..".run"
+ local script = io.open(client_script_path, "w+")
+ if script == nil then
+ mp.msg.error("client script write failed")
+ return
+ else
+ script_written = true
+ script:write(string.format(client_script, options.socket))
+ script:close()
+ subprocess({"chmod", "+x", client_script_path}, true)
+ table.insert(args, "--scripts="..client_script_path)
+ end
+ else
+ local client_script_path = options.socket..".run"
+ table.insert(args, "--scripts="..client_script_path)
+ end
+
+ table.insert(args, "--")
+ table.insert(args, path)
+
+ spawned = true
+ spawn_waiting = true
+
+ subprocess(args, true,
+ function(success, result)
+ if spawn_waiting and (success == false or (result.status ~= 0 and result.status ~= -2)) then
+ spawned = false
+ spawn_waiting = false
+ options.tone_mapping = "no"
+ mp.msg.error("mpv subprocess create failed")
+ if not spawn_working then -- notify users of required configuration
+ if options.mpv_path == "mpv" then
+ if properties["current-vo"] == "libmpv" then
+ if options.mpv_path == mpv_path then -- attempt to locate ImPlay
+ mpv_path = "ImPlay"
+ spawn(time)
+ else -- ImPlay not in path
+ if os_name ~= "darwin" then
+ force_disabled = true
+ info(real_w or effective_w, real_h or effective_h)
+ end
+ mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000)
+ mp.commandv("script-message-to", "implay", "show-message", "thumbfast initial setup", "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay")
+ end
+ else
+ mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000)
+ if os_name == "windows" and frontend_path == nil then
+ mp.commandv("script-message-to", "mpvnet", "show-text", "thumbfast: ERROR! install standalone mpv, see README", 5000, 20)
+ mp.commandv("script-message", "mpv.net", "show-text", "thumbfast: ERROR! install standalone mpv, see README", 5000, 20)
+ end
+ end
+ else
+ mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000)
+ -- found ImPlay but not defined in config
+ mp.commandv("script-message-to", "implay", "show-message", "thumbfast", "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay")
+ end
+ end
+ elseif success == true and (result.status == 0 or result.status == -2) then
+ if not spawn_working and properties["current-vo"] == "libmpv" and options.mpv_path ~= mpv_path then
+ mp.commandv("script-message-to", "implay", "show-message", "thumbfast initial setup", "Set mpv_path=ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay")
+ end
+ spawn_working = true
+ spawn_waiting = false
+ end
+ end
+ )
+end
+
+local function run(command)
+ if not spawned then return end
+
+ if options.direct_io then
+ local hPipe = winapi.C.CreateFileW(winapi.socket_wc, winapi.GENERIC_WRITE, 0, nil, winapi.OPEN_EXISTING, winapi._createfile_pipe_flags, nil)
+ if hPipe ~= winapi.INVALID_HANDLE_VALUE then
+ local buf = command .. "\n"
+ winapi.C.SetNamedPipeHandleState(hPipe, winapi.PIPE_NOWAIT, nil, nil)
+ winapi.C.WriteFile(hPipe, buf, #buf + 1, winapi._lpNumberOfBytesWritten, nil)
+ winapi.C.CloseHandle(hPipe)
+ end
+
+ return
+ end
+
+ local command_n = command.."\n"
+
+ if os_name == "windows" then
+ if file and file_bytes + #command_n >= 4096 then
+ file:close()
+ file = nil
+ file_bytes = 0
+ end
+ if not file then
+ file = io.open("\\\\.\\pipe\\"..options.socket, "r+b")
+ end
+ elseif pre_0_33_0 then
+ subprocess({"/usr/bin/env", "sh", "-c", "echo '" .. command .. "' | socat - " .. options.socket})
+ return
+ elseif not file then
+ file = io.open(options.socket, "r+")
+ end
+ if file then
+ file_bytes = file:seek("end")
+ file:write(command_n)
+ file:flush()
+ end
+end
+
+local function draw(w, h, script)
+ if not w or not show_thumbnail then return end
+ if x ~= nil then
+ local scale_w, scale_h = options.scale_factor ~= 1 and (w * options.scale_factor) or nil, options.scale_factor ~= 1 and (h * options.scale_factor) or nil
+ if pre_0_30_0 then
+ mp.command_native({"overlay-add", options.overlay_id, x, y, options.thumbnail..".bgra", 0, "bgra", w, h, (4*w), scale_w, scale_h})
+ else
+ mp.command_native_async({"overlay-add", options.overlay_id, x, y, options.thumbnail..".bgra", 0, "bgra", w, h, (4*w), scale_w, scale_h}, function() end)
+ end
+ elseif script then
+ local json, err = mp.utils.format_json({width=w, height=h, scale_factor=options.scale_factor, x=x, y=y, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id})
+ mp.commandv("script-message-to", script, "thumbfast-render", json)
+ end
+end
+
+local function real_res(req_w, req_h, filesize)
+ local count = filesize / 4
+ local diff = (req_w * req_h) - count
+
+ if (properties["video-params"] and properties["video-params"]["rotate"] or 0) % 180 == 90 then
+ req_w, req_h = req_h, req_w
+ end
+
+ if diff == 0 then
+ return req_w, req_h
+ else
+ local threshold = 5 -- throw out results that change too much
+ local long_side, short_side = req_w, req_h
+ if req_h > req_w then
+ long_side, short_side = req_h, req_w
+ end
+ for a = short_side, short_side - threshold, -1 do
+ if count % a == 0 then
+ local b = count / a
+ if long_side - b < threshold then
+ if req_h < req_w then return b, a else return a, b end
+ end
+ end
+ end
+ return nil
+ end
+end
+
+local function move_file(from, to)
+ if os_name == "windows" then
+ os.remove(to)
+ end
+ -- move the file because it can get overwritten while overlay-add is reading it, and crash the player
+ os.rename(from, to)
+end
+
+local function seek(fast)
+ if last_seek_time then
+ run("async seek " .. last_seek_time .. (fast and " absolute+keyframes" or " absolute+exact"))
+ end
+end
+
+local seek_period = 3/60
+local seek_period_counter = 0
+local seek_timer
+seek_timer = mp.add_periodic_timer(seek_period, function()
+ if seek_period_counter == 0 then
+ seek(allow_fast_seek)
+ seek_period_counter = 1
+ else
+ if seek_period_counter == 2 then
+ if allow_fast_seek then
+ seek_timer:kill()
+ seek()
+ end
+ else seek_period_counter = seek_period_counter + 1 end
+ end
+end)
+seek_timer:kill()
+
+local function request_seek()
+ if seek_timer:is_enabled() then
+ seek_period_counter = 0
+ else
+ seek_timer:resume()
+ seek(allow_fast_seek)
+ seek_period_counter = 1
+ end
+end
+
+local function check_new_thumb()
+ -- the slave might start writing to the file after checking existance and
+ -- validity but before actually moving the file, so move to a temporary
+ -- location before validity check to make sure everything stays consistant
+ -- and valid thumbnails don't get overwritten by invalid ones
+ local tmp = options.thumbnail..".tmp"
+ move_file(options.thumbnail, tmp)
+ local finfo = mp.utils.file_info(tmp)
+ if not finfo then return false end
+ spawn_waiting = false
+ local w, h = real_res(effective_w, effective_h, finfo.size)
+ if w then -- only accept valid thumbnails
+ move_file(tmp, options.thumbnail..".bgra")
+
+ real_w, real_h = w, h
+ if real_w and (real_w ~= last_real_w or real_h ~= last_real_h) then
+ last_real_w, last_real_h = real_w, real_h
+ info(real_w, real_h)
+ end
+ if not show_thumbnail then
+ file_timer:kill()
+ end
+ return true
+ end
+
+ return false
+end
+
+file_timer = mp.add_periodic_timer(file_check_period, function()
+ if check_new_thumb() then
+ draw(real_w, real_h, script_name)
+ end
+end)
+file_timer:kill()
+
+local function clear()
+ file_timer:kill()
+ seek_timer:kill()
+ if options.quit_after_inactivity > 0 then
+ if show_thumbnail or activity_timer:is_enabled() then
+ activity_timer:kill()
+ end
+ activity_timer:resume()
+ end
+ last_seek_time = nil
+ show_thumbnail = false
+ last_x = nil
+ last_y = nil
+ if script_name then return end
+ if pre_0_30_0 then
+ mp.command_native({"overlay-remove", options.overlay_id})
+ else
+ mp.command_native_async({"overlay-remove", options.overlay_id}, function() end)
+ end
+end
+
+local function quit()
+ activity_timer:kill()
+ if show_thumbnail then
+ activity_timer:resume()
+ return
+ end
+ run("quit")
+ spawned = false
+ real_w, real_h = nil, nil
+ clear()
+end
+
+activity_timer = mp.add_timeout(options.quit_after_inactivity, quit)
+activity_timer:kill()
+
+local function thumb(time, r_x, r_y, script)
+ if disabled then return end
+
+ time = tonumber(time)
+ if time == nil then return end
+
+ if r_x == "" or r_y == "" then
+ x, y = nil, nil
+ else
+ x, y = math.floor(r_x + 0.5), math.floor(r_y + 0.5)
+ end
+
+ script_name = script
+ if last_x ~= x or last_y ~= y or not show_thumbnail then
+ show_thumbnail = true
+ last_x, last_y = x, y
+ draw(real_w, real_h, script)
+ end
+
+ if options.quit_after_inactivity > 0 then
+ if show_thumbnail or activity_timer:is_enabled() then
+ activity_timer:kill()
+ end
+ activity_timer:resume()
+ end
+
+ if time == last_seek_time then return end
+ last_seek_time = time
+ if not spawned then spawn(time) end
+ request_seek()
+ if not file_timer:is_enabled() then file_timer:resume() end
+end
+
+local function watch_changes()
+ if not dirty or not properties["video-out-params"] then return end
+ dirty = false
+
+ local old_w = effective_w
+ local old_h = effective_h
+
+ calc_dimensions()
+
+ local vf_reset = vf_string(filters_reset)
+ local rotate = properties["video-rotate"] or 0
+
+ local resized = old_w ~= effective_w or
+ old_h ~= effective_h or
+ last_vf_reset ~= vf_reset or
+ (last_rotate % 180) ~= (rotate % 180) or
+ par ~= last_par or last_crop ~= properties["video-crop"]
+
+ if resized then
+ last_rotate = rotate
+ info(effective_w, effective_h)
+ elseif last_has_vid ~= has_vid and has_vid ~= 0 then
+ info(effective_w, effective_h)
+ end
+
+ if spawned then
+ if resized then
+ -- mpv doesn't allow us to change output size
+ local seek_time = last_seek_time
+ run("quit")
+ clear()
+ spawned = false
+ spawn(seek_time or mp.get_property_number("time-pos", 0))
+ file_timer:resume()
+ else
+ if rotate ~= last_rotate then
+ run("set video-rotate "..rotate)
+ end
+ local vf_runtime = vf_string(filters_runtime)
+ if vf_runtime ~= last_vf_runtime then
+ run("vf set "..vf_string(filters_all, true))
+ last_vf_runtime = vf_runtime
+ end
+ end
+ else
+ last_vf_runtime = vf_string(filters_runtime)
+ end
+
+ last_vf_reset = vf_reset
+ last_rotate = rotate
+ last_par = par
+ last_crop = properties["video-crop"]
+ last_has_vid = has_vid
+
+ if not spawned and not disabled and options.spawn_first and resized then
+ spawn(mp.get_property_number("time-pos", 0))
+ file_timer:resume()
+ end
+end
+
+local function update_property(name, value)
+ properties[name] = value
+end
+
+local function update_property_dirty(name, value)
+ properties[name] = value
+ dirty = true
+ if name == "tone-mapping" then
+ last_tone_mapping = nil
+ end
+end
+
+local function update_tracklist(name, value)
+ -- current-tracks shim
+ for _, track in ipairs(value) do
+ if track.type == "video" and track.selected then
+ properties["current-tracks/video"] = track
+ return
+ end
+ end
+end
+
+local function sync_changes(prop, val)
+ update_property(prop, val)
+ if val == nil then return end
+
+ if type(val) == "boolean" then
+ if prop == "vid" then
+ has_vid = 0
+ last_has_vid = 0
+ info(effective_w, effective_h)
+ clear()
+ return
+ end
+ val = val and "yes" or "no"
+ end
+
+ if prop == "vid" then
+ has_vid = 1
+ end
+
+ if not spawned then return end
+
+ run("set "..prop.." "..val)
+ dirty = true
+end
+
+local function file_load()
+ clear()
+ spawned = false
+ real_w, real_h = nil, nil
+ last_real_w, last_real_h = nil, nil
+ last_tone_mapping = nil
+ last_seek_time = nil
+ if info_timer then
+ info_timer:kill()
+ info_timer = nil
+ end
+
+ calc_dimensions()
+ info(effective_w, effective_h)
+end
+
+local function shutdown()
+ run("quit")
+ remove_thumbnail_files()
+ if os_name ~= "windows" then
+ os.remove(options.socket)
+ os.remove(options.socket..".run")
+ end
+end
+
+local function on_duration(prop, val)
+ allow_fast_seek = (val or 30) >= 30
+end
+
+mp.observe_property("current-tracks/video", "native", function(name, value)
+ if pre_0_33_0 then
+ mp.unobserve_property(update_tracklist)
+ pre_0_33_0 = false
+ end
+ update_property(name, value)
+end)
+
+mp.observe_property("track-list", "native", update_tracklist)
+mp.observe_property("display-hidpi-scale", "native", update_property_dirty)
+mp.observe_property("video-out-params", "native", update_property_dirty)
+mp.observe_property("video-params", "native", update_property_dirty)
+mp.observe_property("vf", "native", update_property_dirty)
+mp.observe_property("tone-mapping", "native", update_property_dirty)
+mp.observe_property("demuxer-via-network", "native", update_property)
+mp.observe_property("stream-open-filename", "native", update_property)
+mp.observe_property("macos-app-activation-policy", "native", update_property)
+mp.observe_property("current-vo", "native", update_property)
+mp.observe_property("video-rotate", "native", update_property)
+mp.observe_property("video-crop", "native", update_property)
+mp.observe_property("path", "native", update_property)
+mp.observe_property("vid", "native", sync_changes)
+mp.observe_property("edition", "native", sync_changes)
+mp.observe_property("duration", "native", on_duration)
+
+mp.register_script_message("thumb", thumb)
+mp.register_script_message("clear", clear)
+
+mp.register_event("file-loaded", file_load)
+mp.register_event("shutdown", shutdown)
+
+mp.register_idle(watch_changes)
diff --git a/ar/.config/mpv/scripts/user-input.lua b/ar/.config/mpv/scripts/user-input.lua
new file mode 100644
index 0000000..656c3ab
--- /dev/null
+++ b/ar/.config/mpv/scripts/user-input.lua
@@ -0,0 +1,890 @@
+local mp = require("mp")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+local options = require("mp.options")
+
+-- Default options
+local opts = {
+ -- All drawing is scaled by this value, including the text borders and the
+ -- cursor. Change it if you have a high-DPI display.
+ scale = 1,
+ -- Set the font used for the REPL and the console. This probably doesn't
+ -- have to be a monospaced font.
+ font = "",
+ -- Set the font size used for the REPL and the console. This will be
+ -- multiplied by "scale."
+ font_size = 16,
+}
+
+options.read_options(opts, "user_input")
+
+local API_VERSION = "0.1.0"
+local API_MAJOR_MINOR = API_VERSION:match("%d+%.%d+")
+
+local co = nil
+local queue = {}
+local active_ids = {}
+local histories = {}
+local request = nil
+
+local line = ""
+
+--[[
+ The below code is a modified implementation of text input from mpv's console.lua:
+ https://github.com/mpv-player/mpv/blob/7ca14d646c7e405f3fb1e44600e2a67fc4607238/player/lua/console.lua
+
+ Modifications:
+ removed support for log messages, sending commands, tab complete, help commands
+ removed update timer
+ Changed esc key to call handle_esc function
+ handle_esc and handle_enter now resume the main coroutine with a response table
+ made history specific to request ids
+ localised all functions - reordered some to fit
+ keybindings use new names
+]]
+--
+
+------------------------------START ORIGINAL MPV CODE-----------------------------------
+----------------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------
+
+-- Copyright (C) 2019 the mpv developers
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+local assdraw = require("mp.assdraw")
+
+local function detect_platform()
+ local o = {}
+ -- Kind of a dumb way of detecting the platform but whatever
+ if mp.get_property_native("options/vo-mmcss-profile", o) ~= o then
+ return "windows"
+ elseif mp.get_property_native("options/macos-force-dedicated-gpu", o) ~= o then
+ return "macos"
+ elseif os.getenv("WAYLAND_DISPLAY") then
+ return "wayland"
+ end
+ return "x11"
+end
+
+-- Pick a better default font for Windows and macOS
+local platform = detect_platform()
+if platform == "windows" then
+ opts.font = "Consolas"
+elseif platform == "macos" then
+ opts.font = "Menlo"
+else
+ opts.font = "monospace"
+end
+
+local repl_active = false
+local insert_mode = false
+local cursor = 1
+local key_bindings = {}
+local global_margin_y = 0
+
+-- Escape a string for verbatim display on the OSD
+local function ass_escape(str)
+ -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
+ -- it isn't followed by a recognised character, so add a zero-width
+ -- non-breaking space
+ str = str:gsub("\\", "\\\239\187\191")
+ str = str:gsub("{", "\\{")
+ str = str:gsub("}", "\\}")
+ -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
+ -- consecutive newlines
+ str = str:gsub("\n", "\239\187\191\\N")
+ -- Turn leading spaces into hard spaces to prevent ASS from stripping them
+ str = str:gsub("\\N ", "\\N\\h")
+ str = str:gsub("^ ", "\\h")
+ return str
+end
+
+-- Render the REPL and console as an ASS OSD
+local function update()
+ local dpi_scale = mp.get_property_native("display-hidpi-scale", 1.0)
+
+ dpi_scale = dpi_scale * opts.scale
+
+ local screenx, screeny, aspect = mp.get_osd_size()
+ screenx = screenx / dpi_scale
+ screeny = screeny / dpi_scale
+
+ -- Clear the OSD if the REPL is not active
+ if not repl_active then
+ mp.set_osd_ass(screenx, screeny, "")
+ return
+ end
+
+ local ass = assdraw.ass_new()
+ local style = "{\\r"
+ .. "\\1a&H00&\\3a&H00&\\4a&H99&"
+ .. "\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&"
+ .. "\\fn"
+ .. opts.font
+ .. "\\fs"
+ .. opts.font_size
+ .. "\\bord1\\xshad0\\yshad1\\fsp0\\q1}"
+
+ local queue_style = "{\\r"
+ .. "\\1a&H00&\\3a&H00&\\4a&H99&"
+ .. "\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&"
+ .. "\\fn"
+ .. opts.font
+ .. "\\fs"
+ .. opts.font_size
+ .. "\\c&H66ccff&"
+ .. "\\bord1\\xshad0\\yshad1\\fsp0\\q1}"
+
+ -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor
+ -- inline with the surrounding text, but it sets the advance to the width
+ -- of the drawing. So the cursor doesn't affect layout too much, make it as
+ -- thin as possible and make it appear to be 1px wide by giving it 0.5px
+ -- horizontal borders.
+ local cheight = opts.font_size * 8
+ local cglyph = "{\\r"
+ .. "\\1a&H44&\\3a&H44&\\4a&H99&"
+ .. "\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&"
+ .. "\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}"
+ .. "m 0 0 l 1 0 l 1 "
+ .. cheight
+ .. " l 0 "
+ .. cheight
+ .. "{\\p0}"
+ local before_cur = ass_escape(line:sub(1, cursor - 1))
+ local after_cur = ass_escape(line:sub(cursor))
+
+ ass:new_event()
+ ass:an(1)
+ ass:pos(2, screeny - 2 - global_margin_y * screeny)
+
+ if #queue == 2 then
+ ass:append(queue_style .. string.format("There is 1 more request queued\\N"))
+ elseif #queue > 2 then
+ ass:append(queue_style .. string.format("There are %d more requests queued\\N", #queue - 1))
+ end
+ ass:append(style .. request.text .. "\\N")
+ ass:append("> " .. before_cur)
+ ass:append(cglyph)
+ ass:append(style .. after_cur)
+
+ -- Redraw the cursor with the REPL text invisible. This will make the
+ -- cursor appear in front of the text.
+ ass:new_event()
+ ass:an(1)
+ ass:pos(2, screeny - 2)
+ ass:append(style .. "{\\alpha&HFF&}> " .. before_cur)
+ ass:append(cglyph)
+ ass:append(style .. "{\\alpha&HFF&}" .. after_cur)
+
+ mp.set_osd_ass(screenx, screeny, ass.text)
+end
+
+-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
+-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
+local function next_utf8(str, pos)
+ if pos > str:len() then
+ return pos
+ end
+ repeat
+ pos = pos + 1
+ until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
+
+-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
+local function prev_utf8(str, pos)
+ if pos <= 1 then
+ return pos
+ end
+ repeat
+ pos = pos - 1
+ until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
+
+-- Insert a character at the current cursor position (any_unicode)
+local function handle_char_input(c)
+ if insert_mode then
+ line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
+ else
+ line = line:sub(1, cursor - 1) .. c .. line:sub(cursor)
+ end
+ cursor = cursor + #c
+ update()
+end
+
+-- Remove the character behind the cursor (Backspace)
+local function handle_backspace()
+ if cursor <= 1 then
+ return
+ end
+ local prev = prev_utf8(line, cursor)
+ line = line:sub(1, prev - 1) .. line:sub(cursor)
+ cursor = prev
+ update()
+end
+
+-- Remove the character in front of the cursor (Del)
+local function handle_del()
+ if cursor > line:len() then
+ return
+ end
+ line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
+ update()
+end
+
+-- Toggle insert mode (Ins)
+local function handle_ins()
+ insert_mode = not insert_mode
+end
+
+-- Move the cursor to the next character (Right)
+local function next_char(amount)
+ cursor = next_utf8(line, cursor)
+ update()
+end
+
+-- Move the cursor to the previous character (Left)
+local function prev_char(amount)
+ cursor = prev_utf8(line, cursor)
+ update()
+end
+
+-- Clear the current line (Ctrl+C)
+local function clear()
+ line = ""
+ cursor = 1
+ insert_mode = false
+ request.history.pos = #request.history.list + 1
+ update()
+end
+
+-- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D)
+local function maybe_exit()
+ if line == "" then
+ else
+ handle_del()
+ end
+end
+
+local function handle_esc()
+ coroutine.resume(co, {
+ line = nil,
+ err = "exited",
+ })
+end
+
+-- Run the current command and clear the line (Enter)
+local function handle_enter()
+ if request.history.list[#request.history.list] ~= line and line ~= "" then
+ request.history.list[#request.history.list + 1] = line
+ end
+ coroutine.resume(co, {
+ line = line,
+ })
+end
+
+-- Go to the specified position in the command history
+local function go_history(new_pos)
+ local old_pos = request.history.pos
+ request.history.pos = new_pos
+
+ -- Restrict the position to a legal value
+ if request.history.pos > #request.history.list + 1 then
+ request.history.pos = #request.history.list + 1
+ elseif request.history.pos < 1 then
+ request.history.pos = 1
+ end
+
+ -- Do nothing if the history position didn't actually change
+ if request.history.pos == old_pos then
+ return
+ end
+
+ -- If the user was editing a non-history line, save it as the last history
+ -- entry. This makes it much less frustrating to accidentally hit Up/Down
+ -- while editing a line.
+ if old_pos == #request.history.list + 1 and line ~= "" and request.history.list[#request.history.list] ~= line then
+ request.history.list[#request.history.list + 1] = line
+ end
+
+ -- Now show the history line (or a blank line for #history + 1)
+ if request.history.pos <= #request.history.list then
+ line = request.history.list[request.history.pos]
+ else
+ line = ""
+ end
+ cursor = line:len() + 1
+ insert_mode = false
+ update()
+end
+
+-- Go to the specified relative position in the command history (Up, Down)
+local function move_history(amount)
+ go_history(request.history.pos + amount)
+end
+
+-- Go to the first command in the command history (PgUp)
+local function handle_pgup()
+ go_history(1)
+end
+
+-- Stop browsing history and start editing a blank line (PgDown)
+local function handle_pgdown()
+ go_history(#request.history.list + 1)
+end
+
+-- Move to the start of the current word, or if already at the start, the start
+-- of the previous word. (Ctrl+Left)
+local function prev_word()
+ -- This is basically the same as next_word() but backwards, so reverse the
+ -- string in order to do a "backwards" find. This wouldn't be as annoying
+ -- to do if Lua didn't insist on 1-based indexing.
+ cursor = line:len() - select(2, line:reverse():find("%s*[^%s]*", line:len() - cursor + 2)) + 1
+ update()
+end
+
+-- Move to the end of the current word, or if already at the end, the end of
+-- the next word. (Ctrl+Right)
+local function next_word()
+ cursor = select(2, line:find("%s*[^%s]*", cursor)) + 1
+ update()
+end
+
+-- Move the cursor to the beginning of the line (HOME)
+local function go_home()
+ cursor = 1
+ update()
+end
+
+-- Move the cursor to the end of the line (END)
+local function go_end()
+ cursor = line:len() + 1
+ update()
+end
+
+-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
+local function del_word()
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ before_cur = before_cur:gsub("[^%s]+%s*$", "", 1)
+ line = before_cur .. after_cur
+ cursor = before_cur:len() + 1
+ update()
+end
+
+-- Delete from the cursor to the end of the word (Ctrl+Del)
+local function del_next_word()
+ if cursor > line:len() then
+ return
+ end
+
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ after_cur = after_cur:gsub("^%s*[^%s]+", "", 1)
+ line = before_cur .. after_cur
+ update()
+end
+
+-- Delete from the cursor to the end of the line (Ctrl+K)
+local function del_to_eol()
+ line = line:sub(1, cursor - 1)
+ update()
+end
+
+-- Delete from the cursor back to the start of the line (Ctrl+U)
+local function del_to_start()
+ line = line:sub(cursor)
+ cursor = 1
+ update()
+end
+
+-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
+local function get_clipboard(clip)
+ if platform == "x11" then
+ local res = utils.subprocess({
+ args = { "xclip", "-selection", clip and "clipboard" or "primary", "-out" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "wayland" then
+ local res = utils.subprocess({
+ args = { "wl-paste", clip and "-n" or "-np" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "windows" then
+ local res = utils.subprocess({
+ args = {
+ "powershell",
+ "-NoProfile",
+ "-Command",
+ [[& {
+ Trap {
+ Write-Error -ErrorRecord $_
+ Exit 1
+ }
+
+ $clip = ""
+ if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
+ $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
+ } else {
+ Add-Type -AssemblyName PresentationCore
+ $clip = [Windows.Clipboard]::GetText()
+ }
+
+ $clip = $clip -Replace "`r",""
+ $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
+ [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
+ }]],
+ },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == "macos" then
+ local res = utils.subprocess({
+ args = { "pbpaste" },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ end
+ return ""
+end
+
+-- Paste text from the window-system's clipboard. 'clip' determines whether the
+-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
+local function paste(clip)
+ local text = get_clipboard(clip)
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+ line = before_cur .. text .. after_cur
+ cursor = cursor + text:len()
+ update()
+end
+
+-- List of input bindings. This is a weird mashup between common GUI text-input
+-- bindings and readline bindings.
+local function get_bindings()
+ local bindings = {
+ { "esc", handle_esc },
+ { "enter", handle_enter },
+ { "kp_enter", handle_enter },
+ {
+ "shift+enter",
+ function()
+ handle_char_input("\n")
+ end,
+ },
+ { "ctrl+j", handle_enter },
+ { "ctrl+m", handle_enter },
+ { "bs", handle_backspace },
+ { "shift+bs", handle_backspace },
+ { "ctrl+h", handle_backspace },
+ { "del", handle_del },
+ { "shift+del", handle_del },
+ { "ins", handle_ins },
+ {
+ "shift+ins",
+ function()
+ paste(false)
+ end,
+ },
+ {
+ "mbtn_mid",
+ function()
+ paste(false)
+ end,
+ },
+ {
+ "left",
+ function()
+ prev_char()
+ end,
+ },
+ {
+ "ctrl+b",
+ function()
+ prev_char()
+ end,
+ },
+ {
+ "right",
+ function()
+ next_char()
+ end,
+ },
+ {
+ "ctrl+f",
+ function()
+ next_char()
+ end,
+ },
+ {
+ "up",
+ function()
+ move_history(-1)
+ end,
+ },
+ {
+ "ctrl+p",
+ function()
+ move_history(-1)
+ end,
+ },
+ {
+ "wheel_up",
+ function()
+ move_history(-1)
+ end,
+ },
+ {
+ "down",
+ function()
+ move_history(1)
+ end,
+ },
+ {
+ "ctrl+n",
+ function()
+ move_history(1)
+ end,
+ },
+ {
+ "wheel_down",
+ function()
+ move_history(1)
+ end,
+ },
+ { "wheel_left", function() end },
+ { "wheel_right", function() end },
+ { "ctrl+left", prev_word },
+ { "alt+b", prev_word },
+ { "ctrl+right", next_word },
+ { "alt+f", next_word },
+ { "ctrl+a", go_home },
+ { "home", go_home },
+ { "ctrl+e", go_end },
+ { "end", go_end },
+ { "pgup", handle_pgup },
+ { "pgdwn", handle_pgdown },
+ { "ctrl+c", clear },
+ { "ctrl+d", maybe_exit },
+ { "ctrl+k", del_to_eol },
+ { "ctrl+u", del_to_start },
+ {
+ "ctrl+v",
+ function()
+ paste(true)
+ end,
+ },
+ {
+ "meta+v",
+ function()
+ paste(true)
+ end,
+ },
+ { "ctrl+bs", del_word },
+ { "ctrl+w", del_word },
+ { "ctrl+del", del_next_word },
+ { "alt+d", del_next_word },
+ {
+ "kp_dec",
+ function()
+ handle_char_input(".")
+ end,
+ },
+ }
+
+ for i = 0, 9 do
+ bindings[#bindings + 1] = {
+ "kp" .. i,
+ function()
+ handle_char_input("" .. i)
+ end,
+ }
+ end
+
+ return bindings
+end
+
+local function text_input(info)
+ if info.key_text and (info.event == "press" or info.event == "down" or info.event == "repeat") then
+ handle_char_input(info.key_text)
+ end
+end
+
+local function define_key_bindings()
+ if #key_bindings > 0 then
+ return
+ end
+ for _, bind in ipairs(get_bindings()) do
+ -- Generate arbitrary name for removing the bindings later.
+ local name = "_userinput_" .. bind[1]
+ key_bindings[#key_bindings + 1] = name
+ mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true })
+ end
+ mp.add_forced_key_binding("any_unicode", "_userinput_text", text_input, { repeatable = true, complex = true })
+ key_bindings[#key_bindings + 1] = "_userinput_text"
+end
+
+local function undefine_key_bindings()
+ for _, name in ipairs(key_bindings) do
+ mp.remove_key_binding(name)
+ end
+ key_bindings = {}
+end
+
+-- Set the REPL visibility ("enable", Esc)
+local function set_active(active)
+ if active == repl_active then
+ return
+ end
+ if active then
+ repl_active = true
+ insert_mode = false
+ define_key_bindings()
+ else
+ clear()
+ repl_active = false
+ undefine_key_bindings()
+ collectgarbage()
+ end
+ update()
+end
+
+mp.observe_property("user-data/osc/margins", "native", function(_, val)
+ if val then
+ global_margins = val
+ else
+ global_margins = { t = 0, b = 0 }
+ end
+ update()
+end)
+
+-- Redraw the REPL when the OSD size changes. This is needed because the
+-- PlayRes of the OSD will need to be adjusted.
+mp.observe_property("osd-width", "native", update)
+mp.observe_property("osd-height", "native", update)
+mp.observe_property("display-hidpi-scale", "native", update)
+
+----------------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------
+-------------------------------END ORIGINAL MPV CODE------------------------------------
+
+--[[
+ sends a response to the original script in the form of a json string
+ it is expected that all requests get a response, if the input is nil then err should say why
+ current error codes are:
+ exited the user closed the input instead of pressing Enter
+ already_queued a request with the specified id was already in the queue
+ cancelled a script cancelled the request
+ replace replaced by another request
+]]
+local function send_response(res)
+ if res.source then
+ mp.commandv("script-message-to", res.source, res.response, (utils.format_json(res)))
+ else
+ mp.commandv("script-message", res.response, (utils.format_json(res)))
+ end
+end
+
+-- push new request onto the queue
+-- if a request with the same id already exists and the queueable flag is not enabled then
+-- a nil result will be returned to the function
+function push_request(req)
+ if active_ids[req.id] then
+ if req.replace then
+ for i, q_req in ipairs(queue) do
+ if q_req.id == req.id then
+ send_response({ err = "replaced", response = q_req.response, source = q_req.source })
+ queue[i] = req
+ if i == 1 then
+ request = req
+ end
+ end
+ end
+ update()
+ return
+ end
+
+ if not req.queueable then
+ send_response({ err = "already_queued", response = req.response, source = req.source })
+ return
+ end
+ end
+
+ table.insert(queue, req)
+ active_ids[req.id] = (active_ids[req.id] or 0) + 1
+ if #queue == 1 then
+ coroutine.resume(co)
+ end
+ update()
+end
+
+-- safely removes an item from the queue and updates the set of active requests
+function remove_request(index)
+ local req = table.remove(queue, index)
+ active_ids[req.id] = active_ids[req.id] - 1
+
+ if active_ids[req.id] == 0 then
+ active_ids[req.id] = nil
+ end
+ return req
+end
+
+--an infinite loop that moves through the request queue
+--uses a coroutine to handle asynchronous operations
+local function driver()
+ while true do
+ while queue[1] do
+ request = queue[1]
+ line = request.default_input
+ cursor = request.cursor_pos
+
+ if repl_active then
+ update()
+ else
+ set_active(true)
+ end
+
+ res = coroutine.yield()
+ if res then
+ res.source, res.response = request.source, request.response
+ send_response(res)
+ remove_request(1)
+ end
+ end
+
+ set_active(false)
+ coroutine.yield()
+ end
+end
+
+co = coroutine.create(driver)
+
+--cancels any input request that returns true for the given predicate function
+local function cancel_input_request(pred)
+ for i = #queue, 1, -1 do
+ if pred(i) then
+ req = remove_request(i)
+ send_response({ err = "cancelled", response = req.response, source = req.source })
+
+ --if we're removing the first item then that means the coroutine is waiting for a response
+ --we will need to tell the coroutine to resume, upon which it will move to the next request
+ --if there is something in the buffer then save it to the history before erasing it
+ if i == 1 then
+ local old_line = line
+ if old_line ~= "" then
+ table.insert(histories[req.id].list, old_line)
+ end
+ clear()
+ coroutine.resume(co)
+ end
+ end
+ end
+end
+
+mp.register_script_message("cancel-user-input/uid", function(uid)
+ cancel_input_request(function(i)
+ return queue[i].response == uid
+ end)
+end)
+
+-- removes all requests with the specified id from the queue
+mp.register_script_message("cancel-user-input/id", function(id)
+ cancel_input_request(function(i)
+ return queue[i].id == id
+ end)
+end)
+
+-- ensures a request has the correct fields and is correctly formatted
+local function format_request_fields(req)
+ assert(req.version, "input requests require an API version string")
+ if not string.find(req.version, API_MAJOR_MINOR, 1, true) then
+ error(("input request has invalid version: expected %s.x, got %s"):format(API_MAJOR_MINOR, req.version))
+ end
+
+ assert(req.response, "input requests require a response string")
+ assert(req.id, "input requests require an id string")
+
+ req.text = ass_escape(req.request_text or "")
+ req.default_input = req.default_input or ""
+ req.cursor_pos = tonumber(req.cursor_pos) or 1
+ req.id = req.id or "mpv"
+
+ if req.cursor_pos ~= 1 then
+ if req.cursor_pos < 1 then
+ req.cursor_pos = 1
+ elseif req.cursor_pos > #req.default_input then
+ req.cursor_pos = #req.default_input + 1
+ end
+ end
+
+ if not histories[req.id] then
+ histories[req.id] = { pos = 1, list = {} }
+ end
+ req.history = histories[req.id]
+ return req
+end
+
+-- updates the fields of a specific request
+mp.register_script_message("update-user-input/uid", function(uid, req_opts)
+ req_opts = utils.parse_json(req_opts)
+ req_opts.response = uid
+ for i, req in ipairs(queue) do
+ if req.response == uid then
+ local success, result = pcall(format_request_fields, req_opts)
+ if not success then
+ return msg.error(result)
+ end
+
+ queue[i] = result
+ if i == 1 then
+ request = queue[1]
+ end
+ update()
+ return
+ end
+ end
+end)
+
+--the function that parses the input requests
+local function input_request(req)
+ req = format_request_fields(req)
+ push_request(req)
+end
+
+-- script message to recieve input requests, get-user-input.lua acts as an interface to call this script message
+mp.register_script_message("request-user-input", function(req)
+ msg.debug(req)
+ req = utils.parse_json(req)
+ local success, err = pcall(input_request, req)
+ if not success then
+ send_response({ err = err, response = req.response, source = req.source })
+ msg.error(err)
+ end
+end)
diff --git a/ar/.config/mpv/scripts/xscreensaver.lua b/ar/.config/mpv/scripts/xscreensaver.lua
new file mode 100644
index 0000000..a54b944
--- /dev/null
+++ b/ar/.config/mpv/scripts/xscreensaver.lua
@@ -0,0 +1,24 @@
+-- this script periodically deactivates xscreensaver
+-- when video playback is active
+
+local function heartbeat()
+ if
+ mp.get_property_native("pause")
+ or mp.get_property_native("idle")
+ or not mp.get_property_native("vo-configured")
+ then
+ return
+ end
+
+ mp.command_native_async({
+ name = "subprocess",
+ args = { "xscreensaver-command", "-deactivate" },
+ capture_stdout = true,
+ }, function() end)
+end
+
+mp.add_periodic_timer(60, heartbeat)
+
+for _, prop in ipairs({ "pause", "idle", "vo-configured" }) do
+ mp.observe_property(prop, nil, heartbeat)
+end
diff --git a/ar/.config/mpv/scripts/youtube-search.lua b/ar/.config/mpv/scripts/youtube-search.lua
new file mode 100644
index 0000000..00244fd
--- /dev/null
+++ b/ar/.config/mpv/scripts/youtube-search.lua
@@ -0,0 +1,419 @@
+--[[
+ This script allows users to search and open youtube results from within mpv.
+ Available at: https://github.com/CogentRedTester/mpv-scripts
+
+ Users can open the search page with Y, and use Y again to open a search.
+ Alternatively, Ctrl+y can be used at any time to open a search.
+ Esc can be used to close the page.
+ Enter will open the selected item, Shift+Enter will append the item to the playlist.
+
+ This script requires that my other scripts `scroll-list` and `user-input` be installed.
+ scroll-list.lua and user-input-module.lua must be in the ~~/script-modules/ directory,
+ while user-input.lua should be loaded by mpv normally.
+
+ https://github.com/CogentRedTester/mpv-scroll-list
+ https://github.com/CogentRedTester/mpv-user-input
+
+ This script also requires a youtube API key to be entered.
+ The API key must be passed to the `API_key` script-opt.
+ A personal API key is free and can be created from:
+ https://console.developers.google.com/apis/api/youtube.googleapis.com/
+
+ The script also requires that curl be in the system path.
+
+ An alternative to using the official youtube API is to use Invidious.
+ This script has experimental support for Invidious searches using the 'invidious',
+ 'API_path', and 'frontend' options. API_path refers to the url of the API the
+ script uses, Invidious API paths are usually in the form:
+ https://domain.name/api/v1/
+ The frontend option is the url to actualy try to load videos from. This
+ can probably be the same as the above url:
+ https://domain.name
+ Since the url syntax seems to be identical between Youtube and Invidious,
+ it should be possible to mix these options, a.k.a. using the Google
+ API to get videos from an Invidious frontend, or to use an Invidious
+ API to get videos from Youtube.
+ The 'invidious' option tells the script that the API_path is for an
+ Invidious path. This is to support other possible API options in the future.
+]]
+--
+
+local mp = require("mp")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+local opts = require("mp.options")
+
+package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
+local ui = require("user-input-module")
+local list = require("scroll-list")
+
+local o = {
+ API_key = io.popen("pass show api/google-cloud/youtube-search"):read("*a"):gsub("%s+", ""),
+
+ --number of search results to show in the list
+ num_results = 40,
+
+ --the url to send API calls to
+ API_path = "https://www.googleapis.com/youtube/v3/",
+
+ --attempt this API if the default fails
+ fallback_API_path = "",
+
+ --the url to load videos from
+ frontend = "https://www.youtube.com",
+
+ --use invidious API calls
+ invidious = false,
+
+ --whether the fallback uses invidious as well
+ fallback_invidious = false,
+}
+
+opts.read_options(o)
+
+--ensure the URL options are properly formatted
+local function format_options()
+ if o.API_path:sub(-1) ~= "/" then
+ o.API_path = o.API_path .. "/"
+ end
+ if o.fallback_API_path:sub(-1) ~= "/" then
+ o.fallback_API_path = o.fallback_API_path .. "/"
+ end
+ if o.frontend:sub(-1) == "/" then
+ o.frontend = o.frontend:sub(1, -2)
+ end
+end
+
+format_options()
+
+list.header = ("%s Search: \\N-------------------------------------------------"):format(
+ o.invidious and "Invidious" or "Youtube"
+)
+list.num_entries = 17
+list.list_style = [[{\fs10}\N{\q2\fs25\c&Hffffff&}]]
+list.empty_text = "enter search query"
+
+local ass_escape = list.ass_escape
+
+--encodes a string so that it uses url percent encoding
+--this function is based on code taken from here: https://rosettacode.org/wiki/URL_encoding#Lua
+local function encode_string(str)
+ if type(str) ~= "string" then
+ return str
+ end
+ local output, t = str:gsub("[^%w]", function(char)
+ return string.format("%%%X", string.byte(char))
+ end)
+ return output
+end
+
+--convert HTML character codes to the correct characters
+local function html_decode(str)
+ if type(str) ~= "string" then
+ return str
+ end
+
+ return str:gsub("&(#?)(%w-);", function(is_ascii, code)
+ if is_ascii == "#" then
+ return string.char(tonumber(code))
+ end
+ if code == "amp" then
+ return "&"
+ end
+ if code == "quot" then
+ return '"'
+ end
+ if code == "apos" then
+ return "'"
+ end
+ if code == "lt" then
+ return "<"
+ end
+ if code == "gt" then
+ return ">"
+ end
+ return nil
+ end)
+end
+
+--creates a formatted results table from an invidious API call
+function format_invidious_results(response)
+ if not response then
+ return nil
+ end
+ local results = {}
+
+ for i, item in ipairs(response) do
+ if i > o.num_results then
+ break
+ end
+
+ local t = {}
+ table.insert(results, t)
+
+ t.title = html_decode(item.title)
+ t.channelTitle = html_decode(item.author)
+ if item.type == "video" then
+ t.type = "video"
+ t.id = item.videoId
+ elseif item.type == "playlist" then
+ t.type = "playlist"
+ t.id = item.playlistId
+ elseif item.type == "channel" then
+ t.type = "channel"
+ t.id = item.authorId
+ t.title = t.channelTitle
+ end
+ end
+
+ return results
+end
+
+--creates a formatted results table from a youtube API call
+function format_youtube_results(response)
+ if not response or not response.items then
+ return nil
+ end
+ local results = {}
+
+ for _, item in ipairs(response.items) do
+ local t = {}
+ table.insert(results, t)
+
+ t.title = html_decode(item.snippet.title)
+ t.channelTitle = html_decode(item.snippet.channelTitle)
+
+ if item.id.kind == "youtube#video" then
+ t.type = "video"
+ t.id = item.id.videoId
+ elseif item.id.kind == "youtube#playlist" then
+ t.type = "playlist"
+ t.id = item.id.playlistId
+ elseif item.id.kind == "youtube#channel" then
+ t.type = "channel"
+ t.id = item.id.channelId
+ end
+ end
+
+ return results
+end
+
+--sends an API request
+local function send_request(type, queries, API_path)
+ local url = (API_path or o.API_path) .. type
+ url = url .. "?"
+
+ for key, value in pairs(queries) do
+ msg.verbose(key, value)
+ url = url .. "&" .. key .. "=" .. encode_string(value)
+ end
+
+ msg.debug(url)
+ local request = mp.command_native({
+ name = "subprocess",
+ capture_stdout = true,
+ capture_stderr = true,
+ playback_only = false,
+ args = { "curl", url },
+ })
+
+ local response = utils.parse_json(request.stdout)
+ msg.trace(utils.to_string(request))
+
+ if request.status ~= 0 then
+ msg.error(request.stderr)
+ return nil
+ end
+ if not response then
+ msg.error("Could not parse response:")
+ msg.error(request.stdout)
+ return nil
+ end
+ if response.error then
+ msg.error(request.stdout)
+ return nil
+ end
+
+ return response
+end
+
+--sends a search API request - handles Google/Invidious API differences
+local function search_request(queries, API_path, invidious)
+ list.header = ("%s Search: %s\\N-------------------------------------------------"):format(
+ invidious and "Invidious" or "Youtube",
+ ass_escape(queries.q, true)
+ )
+ list.list = {}
+ list.empty_text = "~"
+ list:update()
+ local results = {}
+
+ --we need to modify the returned results so that the rest of the script can read it
+ if invidious then
+ --Invidious searches are done with pages rather than a max result number
+ local page = 1
+ while #results < o.num_results do
+ queries.page = page
+
+ local response = send_request("search", queries, API_path)
+ response = format_invidious_results(response)
+ if not response then
+ msg.warn("Search did not return a results list")
+ return
+ end
+ if #response == 0 then
+ break
+ end
+
+ for _, item in ipairs(response) do
+ table.insert(results, item)
+ end
+
+ page = page + 1
+ end
+ else
+ local response = send_request("search", queries, API_path)
+ results = format_youtube_results(response)
+ end
+
+ --print error messages to console if the API request fails
+ if not results then
+ msg.warn("Search did not return a results list")
+ return
+ end
+
+ list.empty_text = "no results"
+ return results
+end
+
+local function insert_video(item)
+ list:insert({
+ ass = ("%s {\\c&aaaaaa&}%s"):format(ass_escape(item.title), ass_escape(item.channelTitle)),
+ url = ("%s/watch?v=%s"):format(o.frontend, item.id),
+ })
+end
+
+local function insert_playlist(item)
+ list:insert({
+ ass = ("🖿 %s {\\c&aaaaaa&}%s"):format(ass_escape(item.title), ass_escape(item.channelTitle)),
+ url = ("%s/playlist?list=%s"):format(o.frontend, item.id),
+ })
+end
+
+local function insert_channel(item)
+ list:insert({
+ ass = ("👤 %s"):format(ass_escape(item.title)),
+ url = ("%s/channel/%s"):format(o.frontend, item.id),
+ })
+end
+
+local function reset_list()
+ list.selected = 1
+ list:clear()
+end
+
+--creates the search request queries depending on what API we're using
+local function get_search_queries(query, invidious)
+ if invidious then
+ return {
+ q = query,
+ type = "all",
+ page = 1,
+ }
+ else
+ return {
+ key = o.API_key,
+ q = query,
+ part = "id,snippet",
+ maxResults = o.num_results,
+ }
+ end
+end
+
+local function search(query)
+ local response = search_request(get_search_queries(query, o.invidious), o.API_path, o.invidious)
+ if not response and o.fallback_API_path ~= "/" then
+ msg.info("search failed - attempting fallback")
+ response =
+ search_request(get_search_queries(query, o.fallback_invidious), o.fallback_API_path, o.fallback_invidious)
+ end
+
+ if not response then
+ return
+ end
+ reset_list()
+
+ for _, item in ipairs(response) do
+ if item.type == "video" then
+ insert_video(item)
+ elseif item.type == "playlist" then
+ insert_playlist(item)
+ elseif item.type == "channel" then
+ insert_channel(item)
+ end
+ end
+ list:update()
+ list:open()
+end
+
+local function play_result(flag)
+ if not list[list.selected] then
+ return
+ end
+ if flag == "new_window" then
+ mp.commandv("run", "mpv", list[list.selected].url)
+ return
+ end
+
+ mp.commandv("loadfile", list[list.selected].url, flag)
+ if flag == "replace" then
+ list:close()
+ end
+end
+
+table.insert(list.keybinds, {
+ "ENTER",
+ "play",
+ function()
+ play_result("replace")
+ end,
+ {},
+})
+table.insert(list.keybinds, {
+ "Shift+ENTER",
+ "play_append",
+ function()
+ play_result("append-play")
+ end,
+ {},
+})
+table.insert(list.keybinds, {
+ "Ctrl+ENTER",
+ "play_new_window",
+ function()
+ play_result("new_window")
+ end,
+ {},
+})
+
+local function open_search_input()
+ ui.get_user_input(function(input)
+ if not input then
+ return
+ end
+ search(input)
+ end, { request_text = "Enter Query:" })
+end
+
+mp.add_key_binding("Ctrl+/", "yt", open_search_input)
+
+mp.add_key_binding("Y", "youtube-search", function()
+ if not list.hidden then
+ open_search_input()
+ else
+ list:open()
+ if #list.list == 0 then
+ open_search_input()
+ end
+ end
+end)
diff --git a/ar/.config/mpv/scripts/ytdl-preload.lua b/ar/.config/mpv/scripts/ytdl-preload.lua
new file mode 100644
index 0000000..835b25c
--- /dev/null
+++ b/ar/.config/mpv/scripts/ytdl-preload.lua
@@ -0,0 +1,433 @@
+----------------------
+-- #example ytdl_preload.conf
+-- # make sure lines do not have trailing whitespace
+-- # ytdl_opt has no sanity check and should be formatted exactly how it would appear in yt-dlp CLI, they are split into a key/value pair on whitespace
+-- # at least on Windows, do not escape '\' in temp, just us a single one for each divider
+
+-- #temp=R:\ytdltest
+-- #ytdl_opt1=-r 50k
+-- #ytdl_opt2=-N 5
+-- #ytdl_opt#=etc
+----------------------
+local nextIndex
+local caught = true
+-- local pop = false
+local ytdl = "yt-dlp"
+local utils = require("mp.utils")
+local options = require("mp.options")
+local cache = os.getenv("XDG_CACHE_HOME")
+local opts = {
+ temp = cache .. "/mpv",
+ ytdl_opt1 = "",
+ ytdl_opt2 = "",
+ ytdl_opt3 = "",
+ ytdl_opt4 = "",
+ ytdl_opt5 = "",
+ ytdl_opt6 = "",
+ ytdl_opt7 = "",
+ ytdl_opt8 = "",
+ ytdl_opt9 = "",
+}
+options.read_options(opts, "ytdl_preload")
+local additionalOpts = {}
+for k, v in pairs(opts) do
+ if k:find("ytdl_opt%d") and v ~= "" then
+ additionalOpts[k] = v
+ -- print("entry")
+ -- print(k .. v)
+ end
+end
+local cachePath = opts.temp
+
+local chapter_list = {}
+local json = ""
+local filesToDelete = {}
+
+local function exists(file)
+ local ok, err, code = os.rename(file, file)
+ if not ok then
+ if code == 13 then -- Permission denied, but it exists
+ return true
+ end
+ end
+ return ok, err
+end
+--from ytdl_hook
+local function time_to_secs(time_string)
+ local ret
+ local a, b, c = time_string:match("(%d+):(%d%d?):(%d%d)")
+ if a ~= nil then
+ ret = (a * 3600 + b * 60 + c)
+ else
+ a, b = time_string:match("(%d%d?):(%d%d)")
+ if a ~= nil then
+ ret = (a * 60 + b)
+ end
+ end
+ return ret
+end
+local function extract_chapters(data, video_length)
+ local ret = {}
+ for line in data:gmatch("[^\r\n]+") do
+ local time = time_to_secs(line)
+ if time and (time < video_length) then
+ table.insert(ret, { time = time, title = line })
+ end
+ end
+ table.sort(ret, function(a, b)
+ return a.time < b.time
+ end)
+ return ret
+end
+local function chapters()
+ if json.chapters then
+ for i = 1, #json.chapters do
+ local chapter = json.chapters[i]
+ local title = chapter.title or ""
+ if title == "" then
+ title = string.format("Chapter %02d", i)
+ end
+ table.insert(chapter_list, { time = chapter.start_time, title = title })
+ end
+ elseif not (json.description == nil) and not (json.duration == nil) then
+ chapter_list = extract_chapters(json.description, json.duration)
+ end
+end
+--end ytdl_hook
+local title = ""
+local fVideo = ""
+local fAudio = ""
+local function load_files(dtitle, destination, audio, wait)
+ if wait then
+ if exists(destination .. ".mka") then
+ print("---wait success: found mka---")
+ audio = "audio-file=" .. destination .. ".mka,"
+ else
+ print("---could not find mka after wait, audio may be missing---")
+ end
+ end
+ -- if audio ~= "" then
+ -- table.insert(filesToDelete, destination .. ".mka")
+ -- end
+ -- table.insert(filesToDelete, destination .. ".mkv")
+ dtitle = dtitle:gsub("-" .. ("[%w_-]"):rep(11) .. "$", "")
+ dtitle = dtitle:gsub("^" .. ("%d"):rep(10) .. "%-", "")
+ mp.commandv(
+ "loadfile",
+ destination .. ".mkv",
+ "append",
+ audio .. 'force-media-title="' .. dtitle .. '",demuxer-max-back-bytes=1MiB,demuxer-max-bytes=3MiB,ytdl=no'
+ ) --,sub-file="..destination..".en.vtt") --in case they are not set up to autoload
+ mp.commandv("playlist_move", mp.get_property("playlist-count") - 1, nextIndex)
+ mp.commandv("playlist_remove", nextIndex + 1)
+ caught = true
+ title = ""
+ -- pop = true
+end
+
+local listenID = ""
+local function listener(event)
+ if not caught and event.prefix == mp.get_script_name() and string.find(event.text, listenID) then
+ local destination = string.match(event.text, "%[download%] Destination: (.+).mkv")
+ or string.match(event.text, "%[download%] (.+).mkv has already been downloaded")
+ -- if destination then print("---"..cachePath) end;
+ if destination and string.find(destination, string.gsub(cachePath, "~/", "")) then
+ -- print(listenID)
+ mp.unregister_event(listener)
+ _, title = utils.split_path(destination)
+ local audio = ""
+ if fAudio == "" then
+ load_files(title, destination, audio, false)
+ else
+ if exists(destination .. ".mka") then
+ audio = "audio-file=" .. destination .. ".mka,"
+ load_files(title, destination, audio, false)
+ else
+ print("---expected mka but could not find it, waiting for 2 seconds---")
+ mp.add_timeout(2, function()
+ load_files(title, destination, audio, true)
+ end)
+ end
+ end
+ end
+ end
+end
+
+--from ytdl_hook
+mp.add_hook("on_preloaded", 10, function()
+ if string.find(mp.get_property("path"), cachePath) then
+ chapters()
+ if next(chapter_list) ~= nil then
+ mp.set_property_native("chapter-list", chapter_list)
+ chapter_list = {}
+ json = ""
+ end
+ end
+end)
+--end ytdl_hook
+function dump(o)
+ if type(o) == "table" then
+ local s = "{ "
+ for k, v in pairs(o) do
+ if type(k) ~= "number" then
+ k = '"' .. k .. '"'
+ end
+ s = s .. "[" .. k .. "] = " .. dump(v) .. ","
+ end
+ return s .. "} "
+ else
+ return tostring(o)
+ end
+end
+
+local function addOPTS(old)
+ for k, v in pairs(additionalOpts) do
+ -- print(k)
+ if string.find(v, "%s") then
+ for l, w in string.gmatch(v, "([-%w]+) (.+)") do
+ table.insert(old, l)
+ table.insert(old, w)
+ end
+ else
+ table.insert(old, v)
+ end
+ end
+ -- print(dump(old))
+ return old
+end
+
+local AudioDownloadHandle = {}
+local VideoDownloadHandle = {}
+local JsonDownloadHandle = {}
+local function download_files(id, success, result, error)
+ if result.killed_by_us then
+ return
+ end
+ local jfile = cachePath .. "/" .. id .. ".json"
+
+ local jfileIO = io.open(jfile, "w")
+ jfileIO:write(result.stdout)
+ jfileIO:close()
+ json = utils.parse_json(result.stdout)
+ -- print(dump(json))
+ if json.requested_downloads[1].requested_formats ~= nil then
+ local args = {
+ ytdl,
+ "--no-continue",
+ "-q",
+ "-f",
+ fAudio,
+ "--restrict-filenames",
+ "--no-playlist",
+ "--no-part",
+ "-o",
+ cachePath .. "/" .. id .. "-%(title)s-%(id)s.mka",
+ "--load-info-json",
+ jfile,
+ }
+ args = addOPTS(args)
+ AudioDownloadHandle = mp.command_native_async({
+ name = "subprocess",
+ args = args,
+ playback_only = false,
+ }, function() end)
+ else
+ fAudio = ""
+ fVideo = fVideo:gsub("bestvideo", "best")
+ fVideo = fVideo:gsub("bv", "best")
+ end
+
+ local args = {
+ ytdl,
+ "--no-continue",
+ "-f",
+ fVideo .. "/best",
+ "--restrict-filenames",
+ "--no-playlist",
+ "--no-part",
+ "-o",
+ cachePath .. "/" .. id .. "-%(title)s-%(id)s.mkv",
+ "--load-info-json",
+ jfile,
+ }
+ args = addOPTS(args)
+ VideoDownloadHandle = mp.command_native_async({
+ name = "subprocess",
+ args = args,
+ playback_only = false,
+ }, function() end)
+end
+
+local function DL()
+ local index = tonumber(mp.get_property("playlist-pos"))
+ if
+ mp.get_property("playlist/" .. index .. "/filename"):find("/videos$")
+ and mp.get_property("playlist/" .. index + 1 .. "/filename"):find("/shorts$")
+ then
+ return
+ end
+ if
+ tonumber(mp.get_property("playlist-pos-1")) > 0
+ and mp.get_property("playlist-pos-1") ~= mp.get_property("playlist-count")
+ then
+ nextIndex = index + 1
+ local nextFile = mp.get_property("playlist/" .. nextIndex .. "/filename")
+ if nextFile and caught and nextFile:find("://", 0, false) then
+ caught = false
+ mp.enable_messages("info")
+ mp.register_event("log-message", listener)
+ local ytFormat = mp.get_property("ytdl-format")
+ fVideo = string.match(ytFormat, "(.+)%+.+//?") or "bestvideo"
+ fAudio = string.match(ytFormat, ".+%+(.+)//?") or "bestaudio"
+ -- print("start"..nextFile)
+ listenID = tostring(os.time())
+ local args = {
+ ytdl,
+ "--dump-single-json",
+ "--no-simulate",
+ "--skip-download",
+ "--restrict-filenames",
+ "--no-playlist",
+ "--sub-lang",
+ "en",
+ "--write-sub",
+ "--no-part",
+ "-o",
+ cachePath .. "/" .. listenID .. "-%(title)s-%(id)s.%(ext)s",
+ nextFile,
+ }
+ args = addOPTS(args)
+ -- print(dump(args))
+ table.insert(filesToDelete, listenID)
+ JsonDownloadHandle = mp.command_native_async({
+ name = "subprocess",
+ args = args,
+ capture_stdout = true,
+ capture_stderr = true,
+ playback_only = false,
+ }, function(...)
+ download_files(listenID, ...)
+ end)
+ end
+ end
+end
+
+local function clearCache()
+ -- print(pop)
+
+ --if pop == true then
+ mp.abort_async_command(AudioDownloadHandle)
+ mp.abort_async_command(VideoDownloadHandle)
+ mp.abort_async_command(JsonDownloadHandle)
+ -- for k, v in pairs(filesToDelete) do
+ -- print("remove: " .. v)
+ -- os.remove(v)
+ -- end
+ local ftd = io.open(cachePath .. "/temp.files", "a")
+ for k, v in pairs(filesToDelete) do
+ ftd:write(v .. "\n")
+ if package.config:sub(1, 1) ~= "/" then
+ os.execute('del /Q /F "' .. cachePath .. "\\" .. v .. '*"')
+ else
+ os.execute("rm -f " .. cachePath .. "/" .. v .. "*")
+ end
+ end
+ ftd:close()
+ print("clear")
+ mp.command("quit")
+ --end
+end
+mp.add_hook("on_unload", 50, function()
+ -- mp.abort_async_command(AudioDownloadHandle)
+ -- mp.abort_async_command(VideoDownloadHandle)
+ mp.abort_async_command(JsonDownloadHandle)
+ mp.unregister_event(listener)
+ caught = true
+ listenID = "resetYtdlPreloadListener"
+ -- print(listenID)
+end)
+
+local skipInitial
+mp.observe_property("playlist-count", "number", function()
+ if skipInitial then
+ DL()
+ else
+ skipInitial = true
+ end
+end)
+
+--from ytdl_hook
+local platform_is_windows = (package.config:sub(1, 1) == "\\")
+local o = {
+ exclude = "",
+ try_ytdl_first = false,
+ use_manifests = false,
+ all_formats = false,
+ force_all_formats = true,
+ ytdl_path = "",
+}
+local paths_to_search = { "yt-dlp", "yt-dlp_x86", "youtube-dl" }
+--local options = require 'mp.options'
+options.read_options(o, "ytdl_hook")
+
+local separator = platform_is_windows and ";" or ":"
+if o.ytdl_path:match("[^" .. separator .. "]") then
+ paths_to_search = {}
+ for path in o.ytdl_path:gmatch("[^" .. separator .. "]+") do
+ table.insert(paths_to_search, path)
+ end
+end
+
+local function exec(args)
+ local ret = mp.command_native({
+ name = "subprocess",
+ args = args,
+ capture_stdout = true,
+ capture_stderr = true,
+ })
+ return ret.status, ret.stdout, ret, ret.killed_by_us
+end
+
+local msg = require("mp.msg")
+local command = {}
+for _, path in pairs(paths_to_search) do
+ -- search for youtube-dl in mpv's config dir
+ local exesuf = platform_is_windows and ".exe" or ""
+ local ytdl_cmd = mp.find_config_file(path .. exesuf)
+ if ytdl_cmd then
+ msg.verbose("Found youtube-dl at: " .. ytdl_cmd)
+ ytdl = ytdl_cmd
+ break
+ else
+ msg.verbose("No youtube-dl found with path " .. path .. exesuf .. " in config directories")
+ --search in PATH
+ command[1] = path
+ es, json, result, aborted = exec(command)
+ if result.error_string == "init" then
+ msg.verbose("youtube-dl with path " .. path .. exesuf .. " not found in PATH or not enough permissions")
+ else
+ msg.verbose("Found youtube-dl with path " .. path .. exesuf .. " in PATH")
+ ytdl = path
+ break
+ end
+ end
+end
+--end ytdl_hook
+
+mp.register_event("start-file", DL)
+mp.register_event("shutdown", clearCache)
+local ftd = io.open(cachePath .. "/temp.files", "r")
+while ftd ~= nil do
+ local line = ftd:read()
+ if line == nil or line == "" then
+ ftd:close()
+ io.open(cachePath .. "/temp.files", "w"):close()
+ break
+ end
+ -- print("DEL::"..line)
+ if package.config:sub(1, 1) ~= "/" then
+ os.execute('del /Q /F "' .. cachePath .. "\\" .. line .. '*" >nul 2>nul')
+ else
+ os.execute("rm -f " .. cachePath .. "/" .. line .. "* &> /dev/null")
+ end
+end
diff --git a/ar/.config/mpv/unused_scipts/xrandr.lua b/ar/.config/mpv/unused_scipts/xrandr.lua
new file mode 100644
index 0000000..43ce59f
--- /dev/null
+++ b/ar/.config/mpv/unused_scipts/xrandr.lua
@@ -0,0 +1,382 @@
+-- use xrandr command to set output to best fitting fps rate
+-- when playing videos with mpv.
+
+utils = require 'mp.utils'
+
+-- if you want your display output switched to a certain mode during playback,
+-- use e.g. "--script-opts=xrandr-output-mode=1920x1080"
+xrandr_output_mode = mp.get_opt("xrandr-output-mode")
+
+xrandr_blacklist = {}
+function xrandr_parse_blacklist()
+ -- use e.g. "--script-opts=xrandr-blacklist=25" to have xrand.lua not use 25Hz refresh rate
+
+ -- Parse the optional "blacklist" from a string into an array for later use.
+ -- For now, we only support a list of rates, since the "mode" is not subject
+ -- to automatic change (mpv is better at scaling than most displays) and
+ -- this also makes the blacklist option more easy to specify:
+ local b = mp.get_opt("xrandr-blacklist")
+ if (b == nil) then
+ return
+ end
+
+ local i = 1
+ for s in string.gmatch(b, "([^, ]+)") do
+ xrandr_blacklist[i] = 0.0 + s
+ i = i+1
+ end
+end
+xrandr_parse_blacklist()
+
+
+function xrandr_check_blacklist(mode, rate)
+ -- check if (mode, rate) is black-listed - e.g. because the
+ -- computer display output is known to be incompatible with the
+ -- display at this specific mode/rate
+
+ for i=1,#xrandr_blacklist do
+ r = xrandr_blacklist[i]
+
+ if (r == rate) then
+ mp.msg.log("v", "will not use mode '" .. mode .. "' with rate " .. rate .. " because option --script-opts=xrandr-blacklist said so")
+ return true
+ end
+ end
+
+ return false
+end
+
+xrandr_detect_done = false
+xrandr_modes = {}
+xrandr_connected_outputs = {}
+function xrandr_detect_available_rates()
+ if (xrandr_detect_done) then
+ return
+ end
+ xrandr_detect_done = true
+
+ -- invoke xrandr to find out which fps rates are available on which outputs
+
+ local p = {}
+ p["cancellable"] = false
+ p["args"] = {}
+ p["args"][1] = "xrandr"
+ p["args"][2] = "-q"
+ local res = utils.subprocess(p)
+
+ if (res["error"] ~= nil) then
+ mp.msg.log("info", "failed to execute 'xrand -q', error message: " .. res["error"])
+ return
+ end
+
+ mp.msg.log("v","xrandr -q\n" .. res["stdout"])
+
+ local output_idx = 1
+ for output in string.gmatch(res["stdout"], '\n([^ ]+) connected') do
+
+ table.insert(xrandr_connected_outputs, output)
+
+ -- the first line with a "*" after the match contains the rates associated with the current mode
+ local mls = string.match(res["stdout"], "\n" .. string.gsub(output, "%p", "%%%1") .. " connected.*")
+ local r
+ local mode = nil
+ local old_rate
+ local old_mode
+
+ -- old_rate = 0 means "no old rate known to switch to after playback"
+ old_rate = 0
+
+ if (xrandr_output_mode ~= nil) then
+ -- special case: user specified a certain preferred mode to use for playback
+ mp.msg.log("v", "looking for refresh rates for user supplied output mode " .. xrandr_output_mode)
+ mode, r = string.match(mls, '\n (' .. xrandr_output_mode .. ') ([^\n]+)')
+
+ if (mode == nil) then
+ mp.msg.log("info", "user preferred output mode " .. xrandr_output_mode .. " not found for output " .. output .. " - will use current mode")
+ else
+ mp.msg.log("info", "using user preferred xrandr_output_mode " .. xrandr_output_mode .. " for output " .. output)
+ -- try to find the "old rate" for the other, currently active mode
+ local oldr
+ old_mode, oldr = string.match(mls, '\n ([0-9x]+) ([^*\n]*%*[^\n]*)')
+ if (oldr ~= nil) then
+ for s in string.gmatch(oldr, "([^ ]+)%*") do
+ old_rate = s
+ end
+ end
+ mp.msg.log("v", "old_rate=" .. old_rate .. " found for old_mode=" .. tostring(old_mode))
+ end
+ end
+
+ if (mode == nil) then
+ -- normal case: use current mode
+ mode, r = string.match(mls, '\n ([0-9x]+) ([^*\n]*%*[^\n]*)')
+ old_mode = mode
+ end
+
+ if (r == nil) then
+ -- if no refresh rate is reported active for an output by xrandr,
+ -- search for the mode that is "recommended" (marked by "+" in xrandr's output)
+ mode, r = string.match(mls, '\n ([0-9x]+) ([^+\n]*%+[^\n]*)')
+ old_mode = mode
+ if (r == nil) then
+ -- there is not even a "recommended" mode, so let's just use
+ -- whatever first mode line there is
+ mode, r = string.match(mls, '\n ([0-9x]+) ([^+\n]*[^\n]*)')
+ old_mode = mode
+ end
+ else
+ -- so "r" contains a hint to the current ("old") rate, let's remember
+ -- it for later switching back to it.
+ for s in string.gmatch(r, "([^ ]+)%*") do
+ old_rate = s
+ end
+ end
+ mp.msg.log("info", "output " .. output .. " mode=" .. mode .. " old rate=" .. old_rate .. " refresh rates = " .. r)
+
+ xrandr_modes[output] = { mode = mode, old_mode = old_mode, rates_s = r, rates = {}, old_rate = old_rate }
+ local i = 1
+ for s in string.gmatch(r, "([^ +*]+)") do
+
+ -- check if rate "r" is black-listed - this is checked here because
+ if (not xrandr_check_blacklist(mode, 0.0 + s)) then
+ xrandr_modes[output].rates[i] = 0.0 + s
+ i = i+1
+ end
+ end
+
+ output_idx = output_idx + 1
+ end
+
+end
+
+function xrandr_find_best_fitting_rate(fps, output)
+
+ local xrandr_rates = xrandr_modes[output].rates
+
+ local best_fitting_rate = nil
+ local best_fitting_ratio = math.huge
+
+ -- try integer multipliers of 1 to 10 (given that high-fps displays exist these days)
+ for m=1,10 do
+ for i=1,#xrandr_rates do
+ local r = xrandr_rates[i]
+ local ratio = r / (m * fps)
+ if (ratio < 1.0) then
+ ratio = 1.0 / ratio
+ end
+ -- If the ratio is more than "very insignificantly off",
+ -- then add a tiny additional score that will prefer faster
+ -- over slower display frame rates, because those will cause
+ -- shorter "stutters" when the display needs to skip or
+ -- duplicate one source frame.
+ -- If the ratio is very close to 1.0, then we rather not
+ -- choose the higher of the existing display rates, because
+ -- displays performing frame interpolation work better when
+ -- presented the actual, non-repeated source material frames.
+ if (ratio > 1.0001) then
+ ratio = ratio + (0.00000001 * (1000.0 - r))
+ end
+ -- mp.msg.log("info", "ratio " .. ratio .. " for r == " .. r)
+ if (ratio < best_fitting_ratio) then
+ best_fitting_ratio = ratio
+ -- the xrand -q output may print nearby frequencies as the same
+ -- rounded numbers - therefore, if our multiplier is == 1,
+ -- we better return the video's frame rate, which xrandr
+ -- is then likely to set the best rate for, even if the mode
+ -- has some "odd" rate
+ if (m == 1) then
+ r = fps
+ end
+ best_fitting_rate = r
+ end
+ end
+ end
+
+ return best_fitting_rate
+end
+
+
+xrandr_active_outputs = {}
+function xrandr_set_active_outputs()
+ local dn = mp.get_property("display-names")
+
+ if (dn ~= nil) then
+ mp.msg.log("v","display-names=" .. dn)
+ xrandr_active_outputs = {}
+ for w in (dn .. ","):gmatch("([^,]*),") do
+ table.insert(xrandr_active_outputs, w)
+ end
+ end
+end
+
+-- last detected non-nil video frame rate:
+xrandr_cfps = nil
+
+-- for each output, we remember which refresh rate we set last, so
+-- we do not unnecessarily set the same refresh rate again
+xrandr_previously_set = {}
+
+function xrandr_set_rate()
+
+ local f = mp.get_property_native("container-fps")
+ if (f == nil or f == xrandr_cfps) then
+ -- either no change or no frame rate information, so don't set anything
+ return
+ end
+ xrandr_cfps = f
+
+ xrandr_detect_available_rates()
+
+ xrandr_set_active_outputs()
+
+ local vdpau_hack = false
+ local old_vid = nil
+ local old_position = nil
+ if (mp.get_property("options/vo") == "vdpau" or mp.get_property("options/hwdec") == "vdpau") then
+ -- enable wild hack: need to close and re-open video for vdpau,
+ -- because vdpau barfs if xrandr is run while it is in use
+
+ vdpau_hack = true
+ old_position = mp.get_property("time-pos")
+ old_vid = mp.get_property("vid")
+ mp.set_property("vid", "no")
+ end
+
+ -- unless "--script-opts=xrandr-ignore_unknown_oldrate=true" is set,
+ -- xrandr.lua will not touch display outputs for which it cannot
+ -- get information on the current refresh rate for - assuming that
+ -- such outputs are "disabled" somehow.
+ local ignore_unknown_oldrate = mp.get_opt("xrandr-ignore_unknown_oldrate")
+ if (ignore_unknown_oldrate == nil) then
+ ignore_unknown_oldrate = false
+ end
+
+
+ local outs = {}
+ if (#xrandr_active_outputs == 0) then
+ -- No active outputs - probably because vo (like with vdpau) does
+ -- not provide the information which outputs are covered.
+ -- As a fall-back, let's assume all connected outputs are relevant.
+ mp.msg.log("v","no output is known to be used by mpv, assuming all connected outputs are used.")
+ outs = xrandr_connected_outputs
+ else
+ outs = xrandr_active_outputs
+ end
+
+ -- iterate over all relevant outputs used by mpv's output:
+ for n, output in ipairs(outs) do
+
+ if (ignore_unknown_oldrate == false and xrandr_modes[output].old_rate == 0) then
+ mp.msg.log("info", "not touching output " .. output .. " because xrandr did not indicate a used refresh rate for it - use --script-opts=xrandr-ignore_unknown_oldrate=true if that is not what you want.")
+ else
+ local bfr = xrandr_find_best_fitting_rate(xrandr_cfps, output)
+
+ if (bfr == 0.0) then
+ mp.msg.log("info", "no non-blacklisted rate available, not invoking xrandr")
+ else
+ mp.msg.log("info", "container fps is " .. xrandr_cfps .. "Hz, for output " .. output .. " mode " .. xrandr_modes[output].mode .. " the best fitting display rate we will pass to xrandr is " .. bfr .. "Hz")
+
+ if (bfr == xrandr_previously_set[output]) then
+ mp.msg.log("v", "output " .. output .. " was already set to " .. bfr .. "Hz before - not changing")
+ else
+ -- invoke xrandr to set the best fitting refresh rate for output
+ local p = {}
+ p["cancellable"] = false
+ p["args"] = {}
+ p["args"][1] = "xrandr"
+ p["args"][2] = "--output"
+ p["args"][3] = output
+ p["args"][4] = "--mode"
+ p["args"][5] = xrandr_modes[output].mode
+ p["args"][6] = "--rate"
+ p["args"][7] = tostring(bfr)
+
+ local cmd_as_string = ""
+ for k, v in pairs(p["args"]) do
+ cmd_as_string = cmd_as_string .. v .. " "
+ end
+ mp.msg.log("debug", "executing as subprocess: \"" .. cmd_as_string .. "\"")
+ local res = utils.subprocess(p)
+
+ if (res["error"] ~= nil) then
+ mp.msg.log("error", "failed to set refresh rate for output " .. output .. " using xrandr, error message: " .. res["error"])
+ else
+ xrandr_previously_set[output] = bfr
+ end
+ end
+ end
+ end
+ end
+
+ if (vdpau_hack) then
+ mp.set_property("vid", old_vid)
+ if (old_position ~= nil) then
+ mp.commandv("seek", old_position, "absolute", "keyframes")
+ else
+ mp.msg.log("v", "old_position is 'nil' - not seeking after vdpau re-initialization")
+ end
+ end
+end
+
+
+function xrandr_set_old_rate()
+
+ local outs = {}
+ if (#xrandr_active_outputs == 0) then
+ -- No active outputs - probably because vo (like with vdpau) does
+ -- not provide the information which outputs are covered.
+ -- As a fall-back, let's assume all connected outputs are relevant.
+ mp.msg.log("v","no output is known to be used by mpv, assuming all connected outputs are used.")
+ outs = xrandr_connected_outputs
+ else
+ outs = xrandr_active_outputs
+ end
+
+ -- iterate over all relevant outputs used by mpv's output:
+ for n, output in ipairs(outs) do
+
+ local old_rate = xrandr_modes[output].old_rate
+
+ if (old_rate == 0 or xrandr_previously_set[output] == nil ) then
+ mp.msg.log("v", "no previous frame rate known for output " .. output .. " - so no switching back.")
+ else
+
+ if (math.abs(old_rate-xrandr_previously_set[output]) < 0.001) then
+ mp.msg.log("v", "output " .. output .. " is already set to " .. old_rate .. "Hz - no switching back required")
+ else
+
+ mp.msg.log("info", "switching output " .. output .. " that was set for replay to mode " .. xrandr_modes[output].mode .. " at " .. xrandr_previously_set[output] .. "Hz back to mode " .. xrandr_modes[output].old_mode .. " with refresh rate " .. old_rate .. "Hz")
+
+ -- invoke xrandr to set the best fitting refresh rate for output
+ local p = {}
+ p["cancellable"] = false
+ p["args"] = {}
+ p["args"][1] = "xrandr"
+ p["args"][2] = "--output"
+ p["args"][3] = output
+ p["args"][4] = "--mode"
+ p["args"][5] = xrandr_modes[output].old_mode
+ p["args"][6] = "--rate"
+ p["args"][7] = old_rate
+
+ local res = utils.subprocess(p)
+
+ if (res["error"] ~= nil) then
+ mp.msg.log("error", "failed to set refresh rate for output " .. output .. " using xrandr, error message: " .. res["error"])
+ else
+ xrandr_previously_set[output] = old_rate
+ end
+ end
+ end
+
+ end
+
+end
+
+-- we'll consider setting refresh rates whenever the video fps or the active outputs change:
+mp.observe_property("container-fps", "native", xrandr_set_rate)
+mp.observe_property("display-names", "native", xrandr_set_rate)
+
+-- and we'll try to revert the refresh rate when mpv is shut down
+mp.register_event("shutdown", xrandr_set_old_rate)
+
diff --git a/ar/.config/mutt/README.md b/ar/.config/mutt/README.md
new file mode 100644
index 0000000..18dabbb
--- /dev/null
+++ b/ar/.config/mutt/README.md
@@ -0,0 +1,414 @@
+# NeoMutt
+
+Mutt is a text-based mail client renowned for its powerful features. NeoMutt is a command line mail reader (or MUA). It’s a fork of Mutt with added features.
+
+## Server
+
+### Installation
+
+#### Get a Domain Name (https://landchad.net/basic/domain/)
+
+##### Terms
+
+- Domain name
+ The name of a website that you type in an address bar. This site's domain name is LandChad.net.
+- Top-level domain (TLD)
+ The extension of a domain name, like .com, .net, .xyz, etc.
+- Registrar
+ A service authorized to reserve a domain name for you.
+
+When domain names first sell, they usually sell for very cheap, but once someone buys one, they have the rights to it until they decide to sell it, often for much, much more money. Therefore, it's a good idea to reserve a domain name ASAP, even if you didn't intend on doing anything big with it.
+
+So let's register your domain name!
+
+##### How
+
+Domains can be registered at any accredited registrar and there are a lot to choose from. Some major names are Host Gator, Blue Host, Name Cheap or Dream Host.
+
+There are also sites that are more private, like Njalla and Cheap Privacy, which register a domain for you under their name, but still allow you access to it. (Normally all websites must be registered with the ICANN with a real name and address, but these sites allow you to bypass that.)
+
+Choosing a registrar is not permanent, and you can transfer domains to a different registrar if you get a better deal later, so in most cases, you can just choose one and let’s head on…
+
+**Basic info about domain names**
+
+- Domain names usually require a very small yearly fee to keep registered, usually around $12 for most generic TLDs. There are some "specialty" TLDs that are more expensive, but .com, .xyz and other basic TLDs are that cheap.
+- Once you own a domain, it is yours as long as you pay the yearly fee, but you can also sell it to someone for however much you want.
+- Domain names do not hold your data or your website; instead, you add "DNS settings" that direct people connecting to your domain to your IP address. The purpose of a domain name is so that people don't have to remember your IP address to find your website!
+
+**Looking for domain names**
+
+Let's go to our registrar’s site and you can search for domain names.
+
+You can look for whatever domain name you want. Domains that are already bought and owned by someone else might have the option to "Backorder," but it's always best to get one that is unowned, like these:
+Searching for a domain name
+
+Note the differences in prices. Some "specialty" TLDs like .game and .io charge a much larger fee, although you might want one. Some domains above, like .xyz and .org have reduced prices for the first year.
+
+Choose the domain you want and buy it. These .xyz domains are a steal now on sale.
+Buying a domain name
+
+That's all you have to do to own a domain name! As you register a domain, you can also setup an automatic payment to pay your fee yearly to keep your domain. Easy as pie.
+
+Now we will get a server to host your website on.
+
+#### Get a Server
+
+Once you have a domain name, you'll need a server to host all your website files on. In general, a server is just a computer that is constanly broadcasting some services on the internet.
+
+Servers connected to the internet can be extremely useful with or without proper websites attached to them. You can be your own website, email, file-sharing service and much more.
+
+##### Getting a VPS
+
+A Virtual Personal Server (VPS) is a very cheap and easy way to get a web server. Without you having to buy expensive equipment. There are a lot of online businesses that have massive server farms with great internet connection and big power bills that allow you to rent a VPS in that farm for pocket change.
+
+A VPS usually costs $5 a month. Sometimes slightly more, sometimes slightly less. That's a good price for some internet real-estate, but in truth, you can host a huge number of websites and services on a single VPS, so you get a lot more. I might have a dozen websites, an email server, a chat server and a file-sharing services on one VPS.
+
+The VPS provider that I'll be using for this guide is Vultr, since that is what I use. Vultr provides a free one-month $100 credit to anyone who starts an account through this referral link of mine so you can play around with their services with impunity.
+
+##### Starting your server in two minutes or less
+
+[Start an account on Vultr](https://www.vultr.com/?ref=8384069-6G) and let's get started.
+
+Vultr (and other VPS providers) usually give you a choice in where and what exactly your VPS is.
+
+**Server Location**
+
+In general, it doesn't hugely matter what physical location you have your server in. You might theoretically want it close to where you or your audience might be, but if you host a server in Singapore for an American audience, they won't have to be waiting a perceptibly longer time to load the site.
+
+**Some locations might have different abilities and plans than others. For example, in Vultr, their New York location has optional DDOS protection and also has some cheaper $3.50 servers.**
+
+**Operating System/Server Type**
+
+I especially recommend **Debian 11** for an operating system for your server. Debian is the "classic" server OS and as such, **I make my guides on this site for Debian 11**. If you use another OS, just know that your millage may vary in terms of you might need to change some instructions here minorly.
+
+Server size
+
+You finally have a choice in how beefy a server you want. On Vultr, I recommend getting the cheapest option that is not IPv6 only.
+
+Web hosting and even moderately complicated sites do not use huge amounts of RAM or CPU power. If you start doing more intensive stuff than hosting some webpages and an email server and such, you can always bump up your plan on Vultr without data loss (it's not so easy to bump down).
+
+**Additional features**
+
+On Vultr, there are some final checkboxes you can select additional options. You will want to check Enable IPv6 and also Block Storage Compatible.
+
+We will be setting up IPv6 because it's important for future-proofing your website as more of the web moves to the IPv6 protocol. Block storage is the ability (if you want) to later rent large storage disks to connect to your VPS if desired. You just might want that as an option, so it's worth activating now.
+Done!
+
+Once you select those settings, your server will automatically be deployed. Momentarily, you will be able to see your server's IP addresses which will be used for the next brief step:
+
+#### Connect Your Domain and Server with DNS Records
+
+##### The Gist
+
+Now that we have a domain and a server, we can connect the two using DNS records. DNS (domain name system) records are usually put into your registrar and direct people looking up your website to the server where your website and other things will be.
+
+Get your IPv4/IPv6 addresses from your VPS provider and put them into A/AAAA records on your registrar. Simple process, takes a minute, but here's a guide with a million images just so you know.
+
+##### Open up your Registrar
+
+As before, we will be using any registrar of your choice and Vultr as a server host. Go ahead and log into your accounts on both. Open up your registrar, or your registrar, and click on your domain and then a choice for "DNS records." You’ll want to see something like this on your registrar’s site.
+
+Note that we are on the "External Hosts (A, AAAA)" tab by default. There may be default settings set by your registrar. If there are, you can go ahead and delete them so they look clean like the picture above.
+
+**All we have to do now is get our IP addresses from Vultr and add new DNS records that will send connections to our server.**
+
+Keep the registrar tab open and open Vultr and we will copy-and-paste our IP addresses in.
+
+##### Find your server's IP addresses
+
+Looking at your server in the Vultr menu, you should see a number next to it. Mine here is 104.238.126.105 as you can see below the server name (which I have named landchad.net after the domain I will soon attach to it). That is my **IPv4** address.
+
+Copy your IPv4 address and on your registrar’s site, click the "Add Record" record button and add two A entries pasting in your IPv4 address like I've done for mine here.
+
+I add two entries. One has nothing written in the "Host" section. This will direct connections to landchad.net over IPv4 to our IP address. The second has a \* in the "Host" section. This will direct connections to all possible subdomains to the right place too, I mean mail.landchad.net or blog.landchad.net and any other subdomain we might want to add later.
+
+Now let's get our IPv6 address, which is a little more hidden for some reason. IPv6 is important because we are running out of IPv4 addresses, so it is highly important to allow connections via IPv6 as it will be standard in the future. Anyway, now back on Vultr, click on the server name.
+
+On the server settings, **click on settings** and we will see we are on a submenu labeled "IPv4" where we see our IPv4 address again.
+
+Now just click on the **IPv6** submenu to reveal your IPv6 address.
+
+That ugly looking sequence of numbers and letters with colons in between (2001:19f0:5:ccc:5400:03ff:fe58:324a) is my **IPv6** address. Yours will look something like it. Now let's put it into your registrar’s site. This time, be sure to select to put in AAAA records as below:
+
+Now just click "Save Changes." It might take a minute for the DNS settings to propagate across the internet.
+
+##### Test it out!
+
+Now we should have our domain name directing to our new server. We can check by pinging our domain name, check this out:
+
+As you can see, our ping to landchad.net is now being directed to 104.238.128.105. That means we have successfully set up our DNS records! You can also run the command host if you have it, which will list both IPv4 and IPv6 addresses for a domain name.
+
+#### Setting Up an NginX Webserver
+
+At this point, we should have a domain name and a server and the domain name should direct to the IP address of the server with DNS records. As I said in previous articles, the instructions I will give will be for Debian. In this article, other distributions might work a little differently.
+
+##### Logging in to the server
+
+We first want to log into our VPS to get a command prompt where we can set up the web server. I am assuming you are using either MacOS or GNU/Linux and you know how to open a terminal. On Windows, you can also use either PuTTY or the Windows Subsystem for Linux.
+
+Now on Vultr's site, you can click on your VPS and you will see that there is an area that shows you the password for your server at the bottom here.
+
+Now pull up a terminal and type:
+
+```bash
+ssh root@example.org
+```
+
+This command will attempt to log into your server. It should prompt you for your password, and you can just copy or type in the password from Vultr's site.
+
+If you get an error here, you might not have done your DNS settings right. Double check those. Note you can also replace the example.org with your IP address, but you'll want to fix your DNS settings soon.
+
+##### Installing the Webserver: Nginx
+
+If the program runs without an error, ssh has now logged you into your server. Let's start by running the following commands.
+
+```bash
+apt update
+apt upgrade
+apt install nginx
+```
+
+The first command checks for packages that can be updated and the second command installs any updates.
+
+The third command installs nginx (pronounced Engine-X), which is the web server we'll be using, along with some other programs.
+
+**Our nginx configuration file**
+
+nginx is your webserver. You can make a little website or page, put it on your VPS and then tell nginx where it is and how to host it on the internet. It's simple. Let's do it.
+
+nginx configuration files are in /etc/nginx/. The two main subdirectories in there (on Debian and similar OSes) are /etc/nginx/sites-available and /etc/nginx/sites-enabled. The names are descriptive. The idea is that you can make a site configuration file in sites-available and when it's all ready, you make a link/shortcut to it in sites-enabled which will activate it.
+
+First, let's create the settings for our website. You can copy and paste (with required changes) but I will also explain what the lines do.
+
+Create a file in /etc/nginx/sites-available by doing this:
+
+```bash
+nano /etc/nginx/sites-available/mywebsite
+```
+
+Note that "nano" is a command line text editor. You will now be able to create and edit this file. By saving, this file will now appear. Note also I name the file mywebsite, but you can name it whatever you'd like.
+
+I'm going to add the following content to the file. The content like this will be different depending on what you want to call your site.
+
+```bash
+server {
+ listen 80 ;
+ listen [::]:80 ;
+ server_name example.org ;
+ root /var/www/mysite ;
+ index index.html index.htm index.nginx-debian.html ;
+ location / {
+ try_files $uri $uri/ =404 ;
+ }
+}
+```
+
+**Explanation of those settings**
+
+The listen lines tell nginx to listen for connections on both IPv4 and IPv6.
+
+The server_name is the website that we are looking for. By putting landchad.net here, that means whenever someone connects to this server and is looking for that address, they will be directed to the content in this block.
+
+root specifies the directory we're going to put our website files in. This can theoretically be wherever, but it is conventional to have them in /var/www/. Name the directory in that whatever you want.
+
+index determine what the "default" file is; normally when you go to a website, say landchad.net, you are actually going to a file at landchad.net/index.html. That's all that is. Note that that this in concert with the line above mean that /var/www/landchad/index.html, a file on our computer that we'll create, will be the main page of our website.
+
+Lastly, the location block is really just telling the server how to look up files, otherwise throw a 404 error. Location settings are very powerful, but this is all we need them for now.
+
+**Create the directory and index for the site**
+
+We'll actually start making a "real" website later, but let's go ahead and create a little page that will appear when someone looks up the domain.
+
+```base
+mkdir /var/www/mysite
+```
+
+Now let's create an index file inside of that directory, which will appear when the website is accessed:
+
+```bash
+nano /var/www/mysite/index.html
+```
+
+I'll add the following basic content, but you can add whatever you want. This will appear on your website.
+
+```bash
+<!DOCTYPE html>
+<h1>My website!</h1>
+<p>This is my website. Thanks for stopping by!</p>
+<p>Now my website is live!</p>
+```
+
+**Enable the site**
+
+Once you save that file, we can enable it making a link to it in the sites-enabled directory:
+
+```bash
+ln -s /etc/nginx/sites-available/mywebsite /etc/nginx/sites-enabled
+```
+
+Now we can just reload or restart to make nginx service the new configuration:
+
+```bash
+systemctl reload nginx
+```
+
+##### The Firewall
+
+Vultr and some other VPSes automatically install and enable ufw, a firewall program. This will block basically everything by default, so we have to change that. If you don't have ufw installed, you can skip this section.
+
+We must open up at least ports 80 and 443 as below:
+
+```bash
+ufw allow 80
+ufw allow 443
+```
+
+Port 80 is the canonical webserver port, while 443 is the port used for encrypted connections. We will certainly need that for the next page.
+
+> As you add more services to your website, they might need you to open more ports, but that will be mentioned on individual articles. (It should be noted that some local services run only for other services on your machine, so you don’t need to open ports for every process running locally, only those that directly interact with the internet, although it’s common to run those through Nginx for simplicity and security.)
+
+##### Nginx security hint
+
+By default, Nginx and most other webservers automatically show their version number on error pages. It's a good idea to disable this from happening because if an exploit comes out for your server software, someone could exploit it. Open the main Nginx config file /etc/nginx/nginx.conf and find the line # server_tokens off;. Uncomment it, and reload Nginx.
+
+Remember to _keep your server software up to date_ to get the latest security fixes!
+
+##### We now have a running website!
+
+At this point you can now type in your website in your browser and this webpage will appear!
+The webpage as it appears.
+
+Note the "Not secure" notification. The next brief step is securing encrypted connections to your website.
+
+#### Certbot and HTTPS
+
+Once you have a website, it is extremely important to enable encrypted connections over HTTPS/SSL. You might have no idea what that means, but it's easy to do now that we've set our web server up.
+
+Certbot is a program that automatically creates and deploys the certificates that allow encrypted connections. It used to be painful (and often expensive) to do this, but now it's all free and automatic.
+
+##### Why is encryption important?
+
+- With HTTPS, users' ISPs cannot snoop on what they are looking at on your website. They know that they have connected, but the particular pages they visit are private as everything is encrypted. HTTPS increases user privacy.
+- If you later create usernames and passwords for any service on your site, lack of encryption can compromise that private data! Most well-designed software will automatically prevent any unencrypted connections over the internet.
+- Search engines like Google favor pages with HTTPS over unencrypted HTTP.
+- You get the official-looking green 🔒 symbol in the URL bar in most browsers which makes normies subtly trust your site more.
+
+##### Let's do it!
+
+Note in this picture that a browser accessing your site will say "Not secure" or something else to notify you that we are using an unencrypted HTTP connection rather than an encrypted HTTPS one.
+
+##### Installation
+
+Just run:
+
+```bash
+apt install python3-certbot-nginx
+```
+
+And this will install certbot and its module for nginx.
+
+##### Run
+
+As I mentioned in the previous article, firewalls might interfere with certbot, so you will want to either disable your firewall or at least ensure that it allows connections on ports 80 and 443:
+
+```bash
+ufw allow 80
+ufw allow 443
+
+```
+
+Now let's run certbot:
+
+```bash
+certbot --nginx
+```
+
+The command will ask you for your email. This is so when the certificates need to be renewed in three months, you will get an email about it. You can set the certificates to renew automatically, but it's a good idea to check it the first time to ensure it renewed properly. You can avoid giving your email by running the command with the --register-unsafely-without-email option as well.
+
+Agree to the terms, and optionally consent to give your email to the EFF (I recommend against this obviously).
+
+Once all that is done, it will ask you what domains you want a certificate for. You can just press enter to select all.
+activate HTTPS for a site with certbot
+
+It will take a moment to create the certificate, but afterwards, you will be asked if you want to automatically redirect all connections to be encrypted. Since this is preferable, choose 2 to Redirect.
+redirecting http to encrypted https with certbot
+
+##### Checking for success
+
+You should now be able to go to your website and see that there is a 🔒 lock icon or some other notification that you are now on an encrypted connection.
+A 🔒 symbol symbolizing our new HTTPS layer for our website!
+
+##### Setting up certificate renewal
+
+As I mentioned in passing, the Certbot certificates last for 3 months. To renew certificates, you just have to run certbot --nginx renew and it will renew any certificates close to expiry.
+
+Of course, you don't want to have to remember to log in to renew them every three months, so it's easy to tell the server to automatically run this command. We will use a _cronjob_ for this. Run the following command:
+
+```bash
+crontab -e
+```
+
+There might be a little menu that pops up asking what text editor you prefer when you run this command. If you don't know how to use vim, choose nano, the first option.
+
+This crontab command will open up a file for editing. A crontab is a list of commands that your operating system will run automatically at certain times. We are going to tell it to automatically try to renew our certificates every month so we never have to.
+
+Create a new line at the end of the file and add this content:
+
+```bash
+0 0 1 * * certbot --nginx renew
+```
+
+Save the file and exit to activate this cronjob.
+
+For more on cron and crontabs please _click here!_
+
+You now have a live website on the internet. You can add to it what you wish.
+
+As you add content to your site, there are many other things you can also install linked on _the main page_, and many more improvements, tweaks and bonuses.
+
+### Add Users
+
+```bash
+$ sudo useradd -G mail -m <username>
+$ sudo passwd <username>
+```
+
+### Delete Users
+
+```bash
+$ sudo userdel -r <username>
+```
+
+## Client (mutt-wizard)
+
+Muttwizard is a tool that automatically sets up a neomutt-based minimal email system.
+
+### Add Users
+
+```bash
+$ mw -a <user>@<domain>
+```
+
+### Delete Users
+
+```bash
+$ mw -d <user>@<domain>
+```
+
+## Warning
+
+There is a warning is as follow:
+
+> Warning: SSLType is deprecated. Use TLSType instead.
+
+To resolve this warning, simply replace `SSLType` with `TLSType` in your `~/.config/mbsync/config` file.
+
+```bash
+SSLType IMAPS
+```
+
+with:
+
+```bash
+TLSType IMAPS
+```
diff --git a/ar/.config/ncmpcpp/bindings b/ar/.config/ncmpcpp/bindings
new file mode 100644
index 0000000..696e093
--- /dev/null
+++ b/ar/.config/ncmpcpp/bindings
@@ -0,0 +1,468 @@
+##############################################################
+## This is the example bindings file. Copy it to ##
+## ~/.ncmpcpp/bindings or $XDG_CONFIG_HOME/ncmpcpp/bindings ##
+## and set up your preferences ##
+##############################################################
+#
+#def_key "mouse"
+# mouse_event
+#
+#def_key "up"
+# scroll_up
+#
+#def_key "shift-up"
+# select_item
+# scroll_up
+#
+#def_key "down"
+# scroll_down
+#
+#def_key "shift-down"
+# select_item
+# scroll_down
+#
+#def_key "["
+# scroll_up_album
+#
+#def_key "]"
+# scroll_down_album
+#
+#def_key "{"
+# scroll_up_artist
+#
+#def_key "}"
+# scroll_down_artist
+#
+#def_key "page_up"
+# page_up
+#
+#def_key "page_down"
+# page_down
+#
+#def_key "home"
+# move_home
+#
+#def_key "end"
+# move_end
+#
+#def_key "insert"
+# select_item
+#
+#def_key "enter"
+# enter_directory
+#
+#def_key "enter"
+# toggle_output
+#
+#def_key "enter"
+# run_action
+#
+#def_key "enter"
+# play_item
+#
+#def_key "space"
+# add_item_to_playlist
+#
+#def_key "space"
+# toggle_lyrics_update_on_song_change
+#
+#def_key "space"
+# toggle_visualization_type
+#
+#def_key "delete"
+# delete_playlist_items
+#
+#def_key "delete"
+# delete_browser_items
+#
+#def_key "delete"
+# delete_stored_playlist
+#
+#def_key "right"
+# next_column
+#
+#def_key "right"
+# slave_screen
+#
+#def_key "right"
+# volume_up
+#
+#def_key "+"
+# volume_up
+#
+#def_key "left"
+# previous_column
+#
+#def_key "left"
+# master_screen
+#
+#def_key "left"
+# volume_down
+#
+#def_key "-"
+# volume_down
+#
+#def_key ":"
+# execute_command
+#
+#def_key "tab"
+# next_screen
+#
+#def_key "shift-tab"
+# previous_screen
+#
+#def_key "f1"
+# show_help
+#
+#def_key "1"
+# show_playlist
+#
+#def_key "2"
+# show_browser
+#
+#def_key "2"
+# change_browse_mode
+#
+#def_key "3"
+# show_search_engine
+#
+#def_key "3"
+# reset_search_engine
+#
+#def_key "4"
+# show_media_library
+#
+#def_key "4"
+# toggle_media_library_columns_mode
+#
+#def_key "5"
+# show_playlist_editor
+#
+#def_key "6"
+# show_tag_editor
+#
+#def_key "7"
+# show_outputs
+#
+#def_key "8"
+# show_visualizer
+#
+#def_key "="
+# show_clock
+#
+#def_key "@"
+# show_server_info
+#
+#def_key "s"
+# stop
+#
+#def_key "p"
+# pause
+#
+#def_key ">"
+# next
+#
+#def_key "<"
+# previous
+#
+#def_key "ctrl-h"
+# jump_to_parent_directory
+#
+#def_key "ctrl-h"
+# replay_song
+#
+#def_key "backspace"
+# jump_to_parent_directory
+#
+#def_key "backspace"
+# replay_song
+#
+#def_key "f"
+# seek_forward
+#
+#def_key "b"
+# seek_backward
+#
+#def_key "r"
+# toggle_repeat
+#
+#def_key "z"
+# toggle_random
+#
+#def_key "y"
+# save_tag_changes
+#
+#def_key "y"
+# start_searching
+#
+#def_key "y"
+# toggle_single
+#
+#def_key "R"
+# toggle_consume
+#
+#def_key "Y"
+# toggle_replay_gain_mode
+#
+#def_key "T"
+# toggle_add_mode
+#
+#def_key "|"
+# toggle_mouse
+#
+#def_key "#"
+# toggle_bitrate_visibility
+#
+#def_key "Z"
+# shuffle
+#
+#def_key "x"
+# toggle_crossfade
+#
+#def_key "X"
+# set_crossfade
+#
+#def_key "u"
+# update_database
+#
+#def_key "ctrl-s"
+# sort_playlist
+#
+#def_key "ctrl-s"
+# toggle_browser_sort_mode
+#
+#def_key "ctrl-s"
+# toggle_media_library_sort_mode
+#
+#def_key "ctrl-r"
+# reverse_playlist
+#
+#def_key "ctrl-f"
+# apply_filter
+#
+#def_key "ctrl-_"
+# select_found_items
+#
+#def_key "/"
+# find
+#
+#def_key "/"
+# find_item_forward
+#
+#def_key "?"
+# find
+#
+#def_key "?"
+# find_item_backward
+#
+#def_key "."
+# next_found_item
+#
+#def_key ","
+# previous_found_item
+#
+#def_key "w"
+# toggle_find_mode
+#
+#def_key "e"
+# edit_song
+#
+#def_key "e"
+# edit_library_tag
+#
+#def_key "e"
+# edit_library_album
+#
+#def_key "e"
+# edit_directory_name
+#
+#def_key "e"
+# edit_playlist_name
+#
+#def_key "e"
+# edit_lyrics
+#
+#def_key "i"
+# show_song_info
+#
+#def_key "I"
+# show_artist_info
+#
+#def_key "g"
+# jump_to_position_in_song
+#
+#def_key "l"
+# show_lyrics
+#
+#def_key "ctrl-v"
+# select_range
+#
+#def_key "v"
+# reverse_selection
+#
+#def_key "V"
+# remove_selection
+#
+#def_key "B"
+# select_album
+#
+#def_key "a"
+# add_selected_items
+#
+#def_key "c"
+# clear_playlist
+#
+#def_key "c"
+# clear_main_playlist
+#
+#def_key "C"
+# crop_playlist
+#
+#def_key "C"
+# crop_main_playlist
+#
+#def_key "m"
+# move_sort_order_up
+#
+#def_key "m"
+# move_selected_items_up
+#
+#def_key "n"
+# move_sort_order_down
+#
+#def_key "n"
+# move_selected_items_down
+#
+#def_key "M"
+# move_selected_items_to
+#
+#def_key "A"
+# add
+#
+#def_key "S"
+# save_playlist
+#
+#def_key "o"
+# jump_to_playing_song
+#
+#def_key "G"
+# jump_to_browser
+#
+#def_key "G"
+# jump_to_playlist_editor
+#
+#def_key "~"
+# jump_to_media_library
+#
+#def_key "E"
+# jump_to_tag_editor
+#
+#def_key "U"
+# toggle_playing_song_centering
+#
+#def_key "P"
+# toggle_display_mode
+#
+#def_key "\\"
+# toggle_interface
+#
+#def_key "!"
+# toggle_separators_between_albums
+#
+#def_key "L"
+# toggle_lyrics_fetcher
+#
+#def_key "F"
+# fetch_lyrics_in_background
+#
+#def_key "Z"
+# toggle_fetching_lyrics_in_background
+#
+#def_key "ctrl-l"
+# toggle_screen_lock
+#
+#def_key "`"
+# toggle_library_tag_type
+#
+#def_key "`"
+# refetch_lyrics
+#
+#def_key "`"
+# add_random_items
+#
+#def_key "ctrl-p"
+# set_selected_items_priority
+#
+#def_key "q"
+# quit
+#
+#def_key "f"
+# find
+#
+#def_key "f"
+# find_item_forward
+
+def_key "+"
+ show_clock
+def_key "="
+ volume_up
+def_key "j"
+ scroll_down
+def_key "k"
+ scroll_up
+def_key "ctrl-u"
+ page_up
+def_key "ctrl-d"
+ page_down
+def_key "u"
+ page_up
+def_key "d"
+ page_down
+def_key "h"
+ previous_column
+def_key "l"
+ next_column
+def_key "."
+ show_lyrics
+def_key "n"
+ next_found_item
+def_key "N"
+ previous_found_item
+def_key "J"
+ move_sort_order_down
+def_key "K"
+ move_sort_order_up
+def_key "h"
+ jump_to_parent_directory
+def_key "l"
+ enter_directory
+def_key "l"
+ run_action
+def_key "l"
+ play_item
+def_key "m"
+ show_media_library
+def_key "m"
+ toggle_media_library_columns_mode
+def_key "t"
+ show_tag_editor
+def_key "v"
+ show_visualizer
+def_key "G"
+ move_end
+def_key "g"
+ move_home
+def_key "U"
+ update_database
+def_key "s"
+ reset_search_engine
+def_key "s"
+ show_search_engine
+def_key "f"
+ show_browser
+def_key "f"
+ change_browse_mode
+def_key "x"
+ delete_playlist_items
+def_key "P"
+ show_playlist
diff --git a/ar/.config/ncmpcpp/config b/ar/.config/ncmpcpp/config
new file mode 100644
index 0000000..9f342ac
--- /dev/null
+++ b/ar/.config/ncmpcpp/config
@@ -0,0 +1,261 @@
+# vim: filetype=conf
+#
+##### directories ######
+#
+ncmpcpp_directory = "~/.config/ncmpcpp"
+lyrics_directory = "~/.local/share/lyrics"
+#
+#
+##### connection settings #####
+#
+#mpd_host = localhost
+#mpd_port = 6600
+#mpd_connection_timeout = 5
+#mpd_music_dir = ~/music
+mpd_music_dir = "~/Music"
+#mpd_crossfade_time = 5
+#
+#
+##### music visualizer #####
+#
+#visualizer_data_source = /tmp/mpd.fifo
+#visualizer_output_name = Visualizer feed
+#visualizer_in_stereo = yes
+#visualizer_sync_interval = 0
+#
+## Available values: spectrum, wave, wave_filled, ellipse.
+visualizer_type = spectrum
+#
+#visualizer_fps = 60
+#visualizer_autoscale = no
+#visualizer_look = ●▮
+#visualizer_color = blue, cyan, green, yellow, magenta, red
+#visualizer_color = 47, 83, 119, 155, 191, 227, 221, 215, 209, 203, 197, 161
+#visualizer_spectrum_smooth_look = yes
+#visualizer_spectrum_dft_size = 2
+#visualizer_spectrum_gain = 10
+#visualizer_spectrum_hz_min = 20
+#visualizer_spectrum_hz_max = 20000
+#
+#
+##### system encoding #####
+#
+#system_encoding = ""
+#
+#
+##### delays #####
+#
+## (0 = always on).
+#playlist_disable_highlight_delay = 5
+#
+#message_delay_time = 5
+message_delay_time = "1"
+#
+##### song format #####
+##
+## For a song format you can use:
+##
+## %l - length
+## %f - filename
+## %D - directory
+## %a - artist
+## %A - album artist
+## %t - title
+## %b - album
+## %y - date
+## %n - track number (01/12 -> 01)
+## %N - full track info (01/12 -> 01/12)
+## %g - genre
+## %c - composer
+## %p - performer
+## %d - disc
+## %C - comment
+## %P - priority
+## $R - begin right alignment
+##
+## - 0 - default window color (discards all other colors)
+## - 1 - black
+## - 2 - red
+## - 3 - green
+## - 4 - yellow
+## - 5 - blue
+## - 6 - magenta
+## - 7 - cyan
+## - 8 - white
+## - 9 - end of current color
+## - b - bold text
+## - u - underline text
+## - r - reverse colors
+## - a - use alternative character set
+##
+#
+#song_list_format = {$4%a - }{%t}|{$8%f$9}$R{$3(%l)$9}
+song_list_format = {$7%a - $9}{$5%t$9}|{$5%f$9}$R{$6%b $9}{$3%l$9}
+#song_status_format = {{%a{ "%b"{ (%y)}} - }{%t}}|{%f}
+song_status_format = $b{{$8"%t"}} $3by {$4%a{ $3in $7%b{ (%y)}} $3}|{$8%f}
+#song_library_format = {%n - }{%t}|{%f}
+song_library_format = {%n - }{%t}|{%f}
+# alternative_header_first_line_format = $b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b
+alternative_header_first_line_format = $0$aqqu$/a {$6%a$9 - }{$3%t$9}|{$3%f$9} $atqq$/a$9
+alternative_header_second_line_format = {{$4$b%a$/b$9}{ - $7%b$9}{ ($8%y$9)}}|{%D}
+#current_item_prefix = $(yellow)$r
+current_item_prefix = $(cyan)$r$b
+#current_item_suffix = $/r$(end)
+current_item_suffix = $/r$(end)$/b
+current_item_inactive_column_prefix = $(white)$r
+current_item_inactive_column_suffix = $/r$(end)
+now_playing_prefix = "$b> "
+#now_playing_suffix = $/b
+#browser_playlist_prefix = "$2playlist$9 "
+selected_item_prefix = "$6*"
+#selected_item_suffix = $9
+#modified_item_prefix = $3> $9
+## browser_sort_mode are "type", "name", "mtime", "format" and "none".
+#browser_sort_mode = type
+#browser_sort_format = {%a - }{%t}|{%f} {%l}
+#
+#
+##### columns settings #####
+#
+#song_columns_list_format = (20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}
+song_columns_list_format = (20)[cyan]{a} (40)[yellow]{t} (30)[blue]{b} (10)[magenta]{l}
+#
+#
+##### various settings #####
+#
+execute_on_song_change="pkill -RTMIN+11 dwmblocks"
+execute_on_player_state_change="pkill -RTMIN+11 dwmblocks"
+#playlist_show_mpd_host = no
+#playlist_show_remaining_time = no
+#playlist_shorten_total_times = no
+#playlist_separate_albums = no
+#
+##### Display Modes #####
+## Note: Possible display modes: classic, columns.
+playlist_display_mode = columns
+browser_display_mode = columns
+search_engine_display_mode = columns
+playlist_editor_display_mode = columns
+#discard_colors_if_item_is_selected = yes
+#show_duplicate_tags = yes
+#incremental_seeking = yes
+#seek_time = 1
+#volume_change_step = 2
+#autocenter_mode = no
+#centered_cursor = no
+#progressbar_look = =>
+progressbar_look = ->
+#
+## Available values: database, playlist.
+#default_place_to_search_in = database
+#
+## Available values: classic, alternative.
+user_interface = alternative
+#
+#data_fetching_delay = yes
+#media_library_primary_tag = artist
+#
+## Available values: artist, album_artist, date, genre, composer, performer.
+media_library_primary_tag = album_artist
+#
+#media_library_albums_split_by_date = yes
+media_library_albums_split_by_date = no
+#media_library_hide_album_dates = no
+#
+## Available values: wrapped, normal.
+#default_find_mode = wrapped
+#
+#default_tag_editor_pattern = %n - %t
+#header_visibility = yes
+#statusbar_visibility = yes
+#connected_message_on_startup = yes
+#titles_visibility = yes
+#header_text_scrolling = yes
+#cyclic_scrolling = no
+#lyrics_fetchers = azlyrics, genius, musixmatch, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, zeneszoveg, internet
+#follow_now_playing_lyrics = no
+#fetch_lyrics_for_current_song_in_background = no
+#store_lyrics_in_song_dir = no
+#generate_win32_compatible_filenames = yes
+#allow_for_physical_item_deletion = no
+#lastfm_preferred_language = en
+#space_add_mode = add_remove
+#show_hidden_files_in_local_browser = no
+#
+##
+## Screens available for use: help, playlist, browser, search_engine,
+## media_library, playlist_editor, tag_editor, outputs, visualizer, clock,
+## lyrics, last_fm.
+##
+#screen_switcher_mode = playlist, browser
+#
+#startup_screen = playlist
+startup_screen = "media_library"
+#startup_slave_screen = ""
+#startup_slave_screen_focus = no
+#locked_screen_width_part = 50 # Default width of locked screen (in %). Acceptable values are from 20 to 80.
+#ask_for_locked_screen_width_part = yes
+#jump_to_now_playing_song_at_start = yes
+#ask_before_clearing_playlists = yes
+#clock_display_seconds = no
+#display_volume_level = yes
+display_volume_level = no
+#display_bitrate = no
+#display_remaining_time = no
+## Available values: none, basic, extended, perl.
+#regular_expressions = perl
+#ignore_leading_the = no
+ignore_leading_the = yes
+#ignore_diacritics = no
+#block_search_constraints_change_if_items_found = yes
+#mouse_support = yes
+#mouse_list_scroll_whole_page = no
+#lines_scrolled = 5
+#empty_tag_marker = <empty>
+#tags_separator = " | "
+#tag_editor_extended_numeration = no
+#media_library_sort_by_mtime = no
+#enable_window_title = yes
+#
+##
+## - 1 - use mpd built-in searching (no regexes, pattern matching)
+##
+## - 2 - use ncmpcpp searching (pattern matching with support for regexes, but
+## if your mpd is on a remote machine, downloading big database to process
+## it can take a while
+##
+## - 3 - match only exact values (this mode uses mpd function for searching in
+## database and local one for searching in current playlist)
+##
+#
+#search_engine_default_search_mode = 1
+#
+#external_editor = nano
+external_editor = nvim
+use_console_editor = yes
+#
+#
+##### colors definitions #####
+#
+colors_enabled = yes
+#empty_tag_color = cyan
+empty_tag_color = magenta
+header_window_color = cyan
+#volume_color = default
+#state_line_color = default
+#state_flags_color = default:b
+#main_window_color = yellow
+main_window_color = white
+#color1 = white
+#color2 = green
+progressbar_color = black:b
+#progressbar_elapsed_color = green:b
+progressbar_elapsed_color = blue:b
+#statusbar_color = default
+statusbar_color = white
+#statusbar_time_color = default:b
+statusbar_time_color = cyan:b
+#player_state_color = default:b
+#alternative_ui_separator_color = black:b
+#window_border_color = green
+#active_window_border = red
diff --git a/ar/.config/newsboat/config b/ar/.config/newsboat/config
new file mode 100644
index 0000000..a718b41
--- /dev/null
+++ b/ar/.config/newsboat/config
@@ -0,0 +1,60 @@
+# show-read-feeds no
+auto-reload yes
+
+external-url-viewer "urlscan -dc -r 'linkhandler {}'"
+
+bind-key j down
+bind-key k up
+bind-key j next articlelist
+bind-key k prev articlelist
+bind-key J next-feed articlelist
+bind-key K prev-feed articlelist
+bind-key G end
+bind-key g home
+bind-key d pagedown
+bind-key u pageup
+bind-key l open
+bind-key h quit
+bind-key a toggle-article-read
+bind-key n next-unread
+bind-key N prev-unread
+bind-key D pb-download
+bind-key U show-urls
+bind-key x pb-delete
+
+browser linkhandler
+macro o open-in-browser ; -- "Open url based on types"
+macro a set browser "tsp yt-dlp --embed-metadata -xic -f bestvideo*+bestaudio/best --restrict-filenames" ; open-in-browser ; set browser linkhandler ; -- "Add queue to download a file via yt-dlp"
+macro c set browser "clonerepo" ; open-in-browser ; set browser linkhandler ; -- "clone a git repo"
+macro d set browser "dmenuhandler" ; open-in-browser ; set browser linkhandler ; -- "dmenu handler"
+macro e set browser "nvim ~/.config/newsboat/config" ; open-in-browser ; set browser linkhandler ; -- "newsboat config"
+macro l set browser "lynx" ; open-in-browser ; set browser linkhandler ; -- "lynx"
+macro m set browser "mpc add $(yt-dlp -f bestaudio --get-url %u | tail -n 1) && mpc play" ; open-in-browser ; -- "Play url"
+macro M set browser "qndl -m" ; open-in-browser ; set browser linkhandler ; -- "Download a music file via qndl"
+macro t set browser "peertubetorrent %u 480" ; open-in-browser ; set browser linkhandler ; -- "Torrent 480p"
+macro T set browser "peertubetorrent %u 1080" ; open-in-browser ; set browser linkhandler ; -- "Torrent 1080p"
+macro v set browser "setsid -f mpv" ; open-in-browser ; set browser linkhandler ; -- "Play a video"
+macro V set browser "qndl -v" ; open-in-browser ; set browser linkhandler ; -- "Download a video file via qndl"
+macro w set browser "firefox" ; open-in-browser ; set browser linkhandler ; -- "Open url in browser"
+macro y set browser "echo %u | xclip -r -sel c" ; open-in-browser ; set browser linkhandler ; -- "Copy url to clipboard"
+macro Y set browser "youtube-viewer ; --comments=%u" ; open-in-browser ; set browser linkhandler -- "Open youtube comments via youtube-viewer"
+
+color listnormal cyan default
+color listfocus black yellow standout bold
+color listnormal_unread blue default
+color listfocus_unread yellow default bold
+color info red black bold
+color article white default bold
+
+highlight all "---.*---" yellow
+highlight feedlist ".*(0/0))" black
+highlight article "(^Feed:.*|^Title:.*|^Author:.*)" cyan default bold
+highlight article "(^Link:.*|^Date:.*)" default default
+highlight article "https?://[^ ]+" green default
+highlight article "^(Title):.*$" blue default
+highlight article "\\[[0-9][0-9]*\\]" magenta default bold
+highlight article "\\[image\\ [0-9]+\\]" green default bold
+highlight article "\\[embedded flash: [0-9][0-9]*\\]" green default bold
+highlight article ":.*\\(link\\)$" cyan default
+highlight article ":.*\\(image\\)$" blue default
+highlight article ":.*\\(embedded flash\\)$" magenta default
diff --git a/ar/.config/newsboat/urls b/ar/.config/newsboat/urls
new file mode 100644
index 0000000..ecb693b
--- /dev/null
+++ b/ar/.config/newsboat/urls
@@ -0,0 +1,27 @@
+"--- TheSiahxyz ---"
+https://github.com/TheSiahxyz/.dotfiles/commits/master.atom "Git" "~TheSiahxyz dotfiles"
+https://github.com/TheSiahxyz/suckless/commits/master.atom "Git" "~TheSiahxyz suckless"
+" "
+"--- News ---"
+https://artixlinux.org/feed.php "Arch" "Tech" "~Artix Linux Updates"
+https://www.archlinux.org/feeds/news/ "Arch" "Tech" "~Arch Linux Updates"
+" "
+"--- Blog ---"
+https://lukesmith.xyz/rss.xml "Blog" "~Luke Smith"
+" "
+"--- Git ---"
+https://github.com/LukeSmithxyz/voidrice/commits/master.atom "Git" "~Luke Smith dotfiles"
+https://github.com/LukeSmithxyz/mutt-wizard/commits/master.atom "Git" "~Luke Smith mutt-wizard"
+https://github.com/Piotr1215/dotfiles/commits/master.atom "Git" "~Piotr1215 dotfiles"
+https://github.com/linkarzu/dotfiles-latest/commits/main.atom "Git" "~Linkarzu dotfiles"
+" "
+"--- Youtube ---"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCevUmOfLTUX9MNGJQKsPdIA "Algorithm" "Neetcode" "Study" "Tech" "Youtube" "~Neetcode"
+https://www.youtube.com/feeds/videos.xml?channel_id=UC2eYFnH61tmytImy1mTYvhA "Linux" "Tech" "Youtube" "~Luke Smith"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCkWVN7H3JqGtJ5Pv5bvCrAw "Neovim" "Tech" "Youtube" "~Piotr1215"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCrSIvbFncPSlK6AdwE2QboA "Neovim" "Tech" "Youtube" "~Linkarzu"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCNjdEsQSWDgNcvYueavebFQ "Devices" "Info" "Tech" "~3분테크"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCDNvRZRgvkBTUkQzFoT_8rA "Entertainment" "Fun" "Youtube" "~핑계고"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCYJ0Ucu9jPX5kn6SeDcNaIQ "Fun" "LOL" "Stream" "Youtube" "~Wolf"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCBA9XaL5wCdHnC5EmEzwrqw "Fun" "LOL" "Stream" "Youtube" "SOOP" "~김민교"
+https://www.youtube.com/feeds/videos.xml?channel_id=UCkgDHJNdiidw67LAAVooc1A "Fun" "LOL" "Stream" "Youtube" "SOOP" "Full" "~김민교 Full"
diff --git a/ar/.config/npm/.npmrc b/ar/.config/npm/.npmrc
new file mode 100644
index 0000000..9a919b1
--- /dev/null
+++ b/ar/.config/npm/.npmrc
@@ -0,0 +1,2 @@
+cache=~/.cache/npm/cache
+prefix=~/.local/share/npm/global
diff --git a/ar/.config/nsxiv/exec/key-handler b/ar/.config/nsxiv/exec/key-handler
new file mode 100755
index 0000000..fc48110
--- /dev/null
+++ b/ar/.config/nsxiv/exec/key-handler
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+while read -r file; do
+ case "$1" in
+ "h")
+ notify-send "Macro <C-x> key bindings:" "\- b: Set as wallpaper
+- c: Copy file
+- d: Delete file
+- h: Help key bindings
+- f: Flip file horizontally
+- g: Edit in GIMP
+- i: Show file information
+- m: Move file
+- r: Rotate file 90 degrees
+- R: Rotate file -90 degrees
+- s: Resize file
+- S: Change resolution
+- y: Copy file name to clipboard
+- Y: Copy file path to clipboard" &
+ ;;
+ "b") setbg "$file" & ;;
+ "c")
+ [ -z "$destdir" ] && destdir="$(sed "s/#.*$//;/^\s*$/d" ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs | awk '{print $2}' | dmenu -l 20 -i -p "Copy file(s) to where?" | sed "s|~|$HOME|g")"
+ [ ! -d "$destdir" ] && notify-send "$destdir is not a directory, cancelled." && exit
+ cp "$file" "$destdir" && notify-send -i "$(readlink -f "$file")" "$file copied to $destdir." &
+ ;;
+ "d")
+ [ "$(printf "No\\nYes" | dmenu -i -p "Really delete $file?")" = "Yes" ] && rm "$file" && notify-send "$file deleted."
+ ;;
+ "f")
+ magick "$file" -flop "$file"
+ ;;
+ "g") ifinstalled gimp && setsid -f gimp "$file" ;;
+ "i") notify-send "File information" "$(mediainfo "$file" | sed "s/[ ]\+:/:/g;s/: /: <b>/;s/$/<\/b>/" | grep "<b>")" ;;
+ "m")
+ [ -z "$destdir" ] && destdir="$(sed "s/#.*$//;/^\s*$/d" ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs | awk '{print $2}' | dmenu -l 20 -i -p "Move file(s) to where?" | sed "s|~|$HOME|g")"
+ [ ! -d "$destdir" ] && notify-send "$destdir is not a directory, cancelled." && exit
+ mv "$file" "$destdir" && notify-send -i "$(readlink -f "$file")" "$file moved to $destdir." &
+ ;;
+ "r")
+ magick "$file" -rotate 90 "$file"
+ ;;
+ "R")
+ magick "$file" -rotate -90 "$file"
+ ;;
+ "s")
+ size="$(echo "" | dmenu -i -p "Enter a size to resize $file ($(identify -format "%wx%h" $file)): ")"
+ magick "$file" -resize "$size" "$file" && notify-send "$file size has changed to $size."
+ ;;
+ "S")
+ resolution="$(printf low\\nmedium\\nhigh | dmenu -i -p "Choose a resolution: ")"
+ case "$resolution" in
+ low) magick "$file" -resample 72 "$file" ;;
+ medium) magick "$file" -resample 150 "$file" ;;
+ high) magick "$file" -resample 300 "$file" ;;
+ esac && notify-send "Changed $file resolution to $resolution."
+ ;;
+ "y")
+ printf "%s" "$file" | tr -d '\n' | xclip -selection clipboard &&
+ notify-send "$file copied to clipboard" &
+ ;;
+ "Y")
+ readlink -f "$file" | tr -d '\n' | xclip -selection clipboard &&
+ notify-send "$(readlink -f "$file") copied to clipboard" &
+ ;;
+ esac
+done
diff --git a/ar/.config/nvidia.hook b/ar/.config/nvidia.hook
new file mode 100644
index 0000000..3311e43
--- /dev/null
+++ b/ar/.config/nvidia.hook
@@ -0,0 +1,15 @@
+# /etc/pacman.d/hooks/nvidia.hook
+[Trigger]
+Operation=Install
+Operation=Upgrade
+Operation=Remove
+Type=Package
+Target=nvidia
+Target=linux
+
+[Action]
+Description=Update NVIDIA module in initcpio
+Depends=mkinitcpio
+When=PostTransaction
+NeedsTargets
+Exec=/bin/sh -c 'while read -r trg; do case $trg in linux*) exit 0; esac; done; /usr/bin/mkinitcpio -P'
diff --git a/ar/.config/openvpn/thesiah.ovpn b/ar/.config/openvpn/thesiah.ovpn
new file mode 100644
index 0000000..51762c8
--- /dev/null
+++ b/ar/.config/openvpn/thesiah.ovpn
@@ -0,0 +1,78 @@
+client
+proto udp
+explicit-exit-notify
+remote 89.116.191.72 1194
+dev tun
+resolv-retry infinite
+nobind
+persist-key
+persist-tun
+remote-cert-tls server
+verify-x509-name server_WRVoRDKKsUdrI5ZH name
+auth SHA256
+auth-nocache
+cipher AES-128-GCM
+tls-client
+tls-version-min 1.2
+tls-cipher TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256
+ignore-unknown-option block-outside-dns
+setenv opt block-outside-dns # Prevent Windows 10 DNS leak
+verb 3
+<ca>
+-----BEGIN CERTIFICATE-----
+MIIB1zCCAX2gAwIBAgIUIsLW9jacJbbJX/H5cI5VtTH9o7QwCgYIKoZIzj0EAwIw
+HjEcMBoGA1UEAwwTY25fdmt6TDhnakM5Y0pNVDR3OTAeFw0yNDA1MDkwNzA4NTNa
+Fw0zNDA1MDcwNzA4NTNaMB4xHDAaBgNVBAMME2NuX3Zrekw4Z2pDOWNKTVQ0dzkw
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASwoUNEjz3GpbbYsx/oRcKn6dfiI2uN
+gsr6+DzipEHa6XNvp8XSTPJ6u/f2EPFLt18pDADXZBDZJho4z5bQJOLco4GYMIGV
+MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFMWpj76YpYKilfEYxpCuixIggx4yMFkG
+A1UdIwRSMFCAFMWpj76YpYKilfEYxpCuixIggx4yoSKkIDAeMRwwGgYDVQQDDBNj
+bl92a3pMOGdqQzljSk1UNHc5ghQiwtb2Npwltslf8flwjlW1Mf2jtDALBgNVHQ8E
+BAMCAQYwCgYIKoZIzj0EAwIDSAAwRQIgB+QkoED4NG08OBO7dOvrlFrSpMOsO9UD
+zgS6/riY9G4CIQDUsu2qxVTo80kPegH5LwISJ7EcUbSiNl+eb8ZVLY/v9Q==
+-----END CERTIFICATE-----
+</ca>
+<cert>
+-----BEGIN CERTIFICATE-----
+MIIB1jCCAXugAwIBAgIQTLgFt2kWVru7I0CgKD9kBTAKBggqhkjOPQQDAjAeMRww
+GgYDVQQDDBNjbl92a3pMOGdqQzljSk1UNHc5MB4XDTI0MDUwOTA3MDkzM1oXDTI2
+MDgxMjA3MDkzM1owDjEMMAoGA1UEAwwDdnBuMFkwEwYHKoZIzj0CAQYIKoZIzj0D
+AQcDQgAEikqwsBITgpTdbq5dORmC7Jq8lgNoresN3FQyCeBgVxtaNbx4XUQlqUDA
+BF4nic2pan467Jp30Zd9nZuL3gAliqOBqjCBpzAJBgNVHRMEAjAAMB0GA1UdDgQW
+BBTs0caUyIPaBvWMss+tCPjscoGSOzBZBgNVHSMEUjBQgBTFqY++mKWCopXxGMaQ
+rosSIIMeMqEipCAwHjEcMBoGA1UEAwwTY25fdmt6TDhnakM5Y0pNVDR3OYIUIsLW
+9jacJbbJX/H5cI5VtTH9o7QwEwYDVR0lBAwwCgYIKwYBBQUHAwIwCwYDVR0PBAQD
+AgeAMAoGCCqGSM49BAMCA0kAMEYCIQDzcsgl1EgBwiIF/hSQdOSY+NZZzVj1Dkb0
+XWWE0KkjEwIhALWWM95z1u2anbEWYKQGmatQQmsVBg8EQGWC5S6Qncy5
+-----END CERTIFICATE-----
+</cert>
+<key>
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvJ9hESDUG/4xSWiJ
+hrCrk3uuLN6RBy1Em0fazwB5TcGhRANCAASKSrCwEhOClN1url05GYLsmryWA2it
+6w3cVDIJ4GBXG1o1vHhdRCWpQMAEXieJzalqfjrsmnfRl32dm4veACWK
+-----END PRIVATE KEY-----
+</key>
+<tls-crypt>
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+f9949a9e1df8d9ac542d5126f720c773
+1fd53fa01c971139c793d0d501dc8729
+3ac29f19d406e15cd9777b713d4589ef
+6c64de6d3254960cdca9a4d7bcb6aec9
+a14944911074cfb5151ae7c9530217a6
+c380cc2efaaee834d3ba839be3df86f0
+f65aa46e71b58305e59894a08d1f005c
+9d69a70c1f7881fb3dccd5ae4459eda6
+c58a7ea3bf61267d1123f16bb86cd048
+7631f109de55f6c0823b0b770dad6e2b
+01be0c37d1878a6bfea3e1ab7d6b6e49
+5e56a3b15be870fbf5a8c1a2dd2ebdac
+e253f81daa99683ebd9d53f09359a8f3
+cf260ae2a73aba4e50252598a372991b
+5a6dbec4b7a1aad6eb4b4dc3be828d29
+3d6dd6955c421091d8e30effe6af9d99
+-----END OpenVPN Static key V1-----
+</tls-crypt>
diff --git a/ar/.config/pam-gnupg b/ar/.config/pam-gnupg
new file mode 100644
index 0000000..a3150cf
--- /dev/null
+++ b/ar/.config/pam-gnupg
@@ -0,0 +1,2 @@
+FBAE9F51CA060B2A5E377DD75CD76D3C44BE0FD9
+CEA80B05ABA46C5DE584655EFD7D26E81A2DFF65
diff --git a/ar/.config/pinentry/preexec b/ar/.config/pinentry/preexec
new file mode 100755
index 0000000..93603c7
--- /dev/null
+++ b/ar/.config/pinentry/preexec
@@ -0,0 +1,5 @@
+#!/hint/sh
+
+# Define additional functionality for pinentry. For example
+test -e /usr/lib/libgcr-base-3.so.1 && exec /usr/bin/pinentry-gnome3 "$@"
+#test -e /usr/lib/libQt5Widgets.so.5 && exec /usr/bin/pinentry-qt "$@"
diff --git a/ar/.config/pip/pip.conf b/ar/.config/pip/pip.conf
new file mode 100644
index 0000000..9d54ef9
--- /dev/null
+++ b/ar/.config/pip/pip.conf
@@ -0,0 +1,2 @@
+[global]
+break-system-packages = true
diff --git a/ar/.config/pipewire/pipewire.conf.d/user-session.conf b/ar/.config/pipewire/pipewire.conf.d/user-session.conf
new file mode 100644
index 0000000..4cbe1d5
--- /dev/null
+++ b/ar/.config/pipewire/pipewire.conf.d/user-session.conf
@@ -0,0 +1,4 @@
+context.exec = [
+ { path = "/usr/bin/wireplumber" args = "" condition = [ { exec.session-manager = null } { exec.session-manager = true } ] }
+ { path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] }
+]
diff --git a/ar/.config/profanity/profrc b/ar/.config/profanity/profrc
new file mode 100644
index 0000000..707a2a5
--- /dev/null
+++ b/ar/.config/profanity/profrc
@@ -0,0 +1,94 @@
+[alias]
+afk=/status set away
+unafk=/status set online
+consi=/connect si@thesiah.xyz
+jts=/join conference.chat.thesiah.xyz
+yj=/msg yejin@thesiah.xyz hey wassup?
+
+[chatstates]
+enabled=true
+gone=10
+outtype=false
+
+[connection]
+account=si@thesiah.xyz
+autoping=60
+reconnect=5
+receipts.request=false
+receipts.send=false
+
+[logging]
+chlog=true
+grlog=true
+maxsize=1048580
+rotate=true
+shared=true
+
+[notifications]
+invite=true
+message=true
+message.current=true
+message.text=true
+remind=60
+room=true
+room.current=true
+room.mention=true
+room.offline=true
+room.text=true
+sub=true
+typing=true
+typing.current=false
+
+[otr]
+log=redact
+otr.char=@
+policy=manual
+warn=true
+
+[pgp]
+pgp.char=%
+
+[presence]
+autoaway.awaytime=15
+autoaway.awaymessage=Away from computer
+autoaway.check=true
+autoaway.mode=away
+
+[ui]
+beep=false
+color.nick=true
+enc.warn=true
+flash=false
+history=true
+intype=true
+mouse=false
+occupants=true
+occupants.jid=false
+occupants.size=15
+privileges=true
+presence=true
+resource.title=true
+resource.message=true
+roster=true
+roster.offline=true
+roster.resource=false
+roster.empty=true
+roster.by=presence
+roster.size=25
+splash=false
+statuses.chat=all
+statuses.console=all
+statuses.muc=all
+theme=boothj5
+time.statusbar=%H:%M:%S
+titlebar=true
+vercheck=false
+wins.autotidy=true
+wrap=true
+time.console=%d-%m-%y %H:%M
+time.chat=%d-%m-%y %H:%M
+time.muc=%d-%m-%y %H:%M
+time.config=%d-%m-%y %H:%M
+time.private=%d-%m-%y %H:%M
+time.xmlconsole=%d-%m-%y %H:%M
+wintitle.show=true
diff --git a/ar/.config/pulse/daemon.conf b/ar/.config/pulse/daemon.conf
new file mode 100644
index 0000000..ef900f1
--- /dev/null
+++ b/ar/.config/pulse/daemon.conf
@@ -0,0 +1,4 @@
+# Never exit pulseaudio if idle. This is to deal with an issue of Chromium
+# browsers not properly starting Pulseaudio by themselves. When the underlying
+# issue is solved, this file/directory should be removed.
+exit-idle-time = -1
diff --git a/ar/.config/python/pythonrc b/ar/.config/python/pythonrc
new file mode 100644
index 0000000..b32e6b6
--- /dev/null
+++ b/ar/.config/python/pythonrc
@@ -0,0 +1,2 @@
+import readline
+readline.write_history_file = lambda *args: None
diff --git a/ar/.config/sesh/sesh.toml b/ar/.config/sesh/sesh.toml
new file mode 100644
index 0000000..17e6085
--- /dev/null
+++ b/ar/.config/sesh/sesh.toml
@@ -0,0 +1,43 @@
+[default_session]
+startup_command = "if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then git status; else clear && ls -I .git; fi"
+preview_command = "eza -glaa --icons --group-directories-first --color=always {}"
+
+[[session]]
+name = "code"
+path = "~"
+# disable_startup_command = true
+
+[[session]]
+name = "nvim config"
+path = "~/.config/nvim"
+startup_command = "nvim"
+
+[[session]]
+name = "tmux config"
+path = "~/.config/tmux"
+startup_command = "nvim tmux.conf"
+preview_command = "bat --plain --wrap character --color=always ~/.config/tmux/tmux.conf"
+
+[[session]]
+name = "config"
+path = "~/.config"
+
+[[session]]
+name = "dotfiles"
+path = "~/.dotfiles"
+
+[[session]]
+name = "private"
+path = "~/Private"
+
+[[session]]
+name = "public"
+path = "~/Public"
+
+[[session]]
+name = "scripts"
+path = "~/.local/bin"
+
+[[session]]
+name = "suckless"
+path = "~/.local/src/suckless"
diff --git a/ar/.config/shell/aliasrc b/ar/.config/shell/aliasrc
new file mode 100644
index 0000000..0a58ad3
--- /dev/null
+++ b/ar/.config/shell/aliasrc
@@ -0,0 +1,467 @@
+# alias - normal aliases (completed with trailing space)
+# balias - blank aliases (completed without space)
+# ialias - ignored aliases (not completed)
+
+# Use neovim for vim if present.
+[ -x "$(command -v nvim)" ] && alias vimdiff="nvim -d"
+
+# Use $XINITRC variable if file exists.
+[ -f "$XINITRC" ] && alias startx="startx $XINITRC"
+
+# Use $MBSYNCRC variable if file exists to sync mailbox.
+[ -f "$MBSYNCRC" ] && alias mbsync="mbsync -c $MBSYNCRC"
+
+# sudo not required for some system commands
+for command in blkid lsblk mount umount pacman poweroff reboot shutdown su sv updatedb; do
+ alias $command="sudo $command"
+done
+unset command
+
+case "$(readlink -f /sbin/init)" in
+*systemd*)
+ # journal
+ alias -g jctl='journalctl -xe'
+ alias -g jctlou='sudo journalctl -b -n 200 -f'
+ alias -g rpi='systemctl --user restart wireplumber pipewire pipewire-pulse pipewire-jack'
+ alias -g sctl='systemctl'
+ alias -g sctlss='systemctl status'
+ alias -g sctle='systemctl enable'
+ alias -g sctld='systemctl disable'
+ alias -g sctlr='systemctl restart'
+ alias -g sctls='systemctl start'
+ alias -g sctlt='systemctl stop'
+ alias -g sctldr='systemctl daemon-reload'
+ alias -g tctl='timedatectl'
+ ;;
+esac
+
+# Go back
+alias ...='../..'
+alias ....='../../..'
+alias .....='../../../..'
+
+# abook
+alias abook='abook --datafile ~/.config/abook/addressbook'
+
+# bat
+alias bath='bat cache --build'
+alias can='bat -n'
+ialias -g cat='bat --plain --wrap character'
+# alias -g -- -h='-h 2>&1 | bat --language=help --style=plain'
+alias -g -- --help='--help 2>&1 | bat --language=help --style=plain'
+
+# bash
+alias sbs='source ~/.bashrc'
+
+# bc
+alias bc='bc -ql'
+
+# bluetooth
+alias bctl='bluetoothctl'
+
+# cal
+ialias cal='cal -y --monday'
+
+# cd
+alias cf='cd "$(dirname "$(readlink -f health.lua)")"'
+alias pd='cd -'
+
+# chmod
+alias che='find . -type f -exec chmod +x {};'
+alias chfd='find . -type d -exec chmod 755 {}; -o -type f -exec chmod 644 {};'
+alias cx='chmod a+x'
+alias 000='chmod -R 000'
+alias 600='chmod -R 600'
+alias 644='chmod -R 644'
+alias 666='chmod -R 666'
+alias 755='chmod -R 755'
+alias 777='chmod -R 777'
+
+# copy
+alias CC='$(fc -l -n -1) | xclip -selection clipboard'
+ialias cp='cp -iv'
+alias pwdc='pwd | xclip -selection clipboard'
+
+# curl
+ialias curl='curl --silent --show-error'
+balias clh='curl localhost:'
+balias clh8='curl localhost:8080'
+balias clh9='curl localhost:9080'
+balias c100='curl 192.168.99.100:'
+
+# delete
+alias _fd='find . -type f -name "._*" -print0 | xargs -0 rm -f'
+alias _fp='find . -type f -name "._*" -print'
+
+# diff
+ialias diff='diff --color'
+
+# docker
+alias dk='docker'
+alias dkp='docker ps'
+alias dkpa='docker ps -a'
+alias dkpaq='docker ps -a -q'
+alias dkb='docker build -t'
+alias dkbnc='docker build --no-cache -t'
+alias dkr='docker run --rm'
+alias dkrti='docker run --rm -ti'
+alias dkrd='docker run -d'
+alias dkrp8='docker run --rm -p 8080:8080'
+alias dkrp9='docker run --rm -p 9080:9080'
+alias dks='docker start'
+alias dkt='docker stop'
+alias dktt='docker stop $(docker ps -q)'
+alias dkk='docker kill'
+alias dkkk='docker kill $(docker ps -q)'
+alias dkrm='docker rm'
+alias dkri='docker rmi'
+alias dke='docker exec -ti'
+alias dkl='docker logs -f'
+alias dki='docker images'
+alias dkpu='docker pull'
+alias dkph='docker push'
+alias dkin='docker inspect'
+alias dkn='docker network'
+alias dkc='docker-compose'
+alias dkcu='docker-compose up'
+alias dkclean='docker ps -q -a -f status=exited | xargs -r docker rm && docker images -q -f dangling=true | xargs -r docker rmi'
+
+# dotbare
+alias dg='dotbare'
+alias dga='dotbare add'
+alias dgaa='dotbare add .'
+alias dgcm='dotbare commit -m'
+alias dgcam='dotbare commit -am'
+alias dgd='dotbare diff'
+alias dgl='dotbare log'
+alias dgp='dotbare push'
+alias dgst='dotbare status'
+
+# # ecryptfs
+# alias emt="echo "$(pass show encryption/ecryptfs)" | sudo mount -t ecryptfs "$1" "$2" \
+# -o ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=no,ecryptfs_enable_filename_crypto=yes, \
+# ecryptfs_sig="$(sudo cat /root/.ecryptfs/sig-cache.txt)", \
+# ecryptfs_fnek_sig="$(sudo cat /root/.ecryptfs/sig-cache.txt)", \
+# passwd="$(printf '%s' "$(pass show encryption/ecryptfs)")""
+
+# ls: eza or built-in
+[ -x "$(command -v eza)" ] && {
+ ialias l='eza --icons --group-directories-first'
+ ialias la='eza --icons -aa --group-directories-first'
+ ialias lh='eza --icons -aa --group-directories-first'
+ ialias ll='eza -gl --icons --group-directories-first'
+ ialias lla='eza -glaa --icons --group-directories-first'
+ ialias lm='eza -glA --group-directories-first | more'
+ ialias lr='eza --icons -R --group-directories-first'
+ ialias ls='eza --icons -A --group-directories-first'
+ ialias lsb='eza --icons -b --group-directories-first'
+ ialias lsby='eza --icons -B --group-directories-first'
+ ialias lld='eza --icons -Dl --group-directories-first'
+ ialias llda='eza --icons -aaDl --group-directories-first'
+ ialias llf='eza --icons -fl'
+ ialias llfa='eza --icons -aafl'
+ ialias llsa='eza --icons -l -s=accessed'
+ ialias llsaa='eza --icons -aal -s=accessed'
+ ialias llsc='eza --icons -l -s=created'
+ ialias llsca='eza --icons -aal -s=created'
+ ialias llse='eza --icons -l -s=extension'
+ ialias llsea='eza --icons -aal -s=extension'
+ ialias llsm='eza --icons -l -s=modified'
+ ialias llsma='eza --icons -aal -s=modified'
+ ialias llsn='eza --icons -l -s=name'
+ ialias llsna='eza --icons -aal -s=name'
+ ialias llss='eza --icons -l -s=size'
+ ialias llssa='eza --icons -aal -s=size'
+ ialias llst='eza --icons -l -s=type'
+ ialias llsta='eza --icons -aal -s=type'
+ ialias lt='eza --icons -T -L'
+ ialias ltd='eza --icons -TD -L'
+ ialias ltdr='eza --icons -TDr -L'
+ ialias ltr='eza --icons -Tr -L'
+} || {
+ ialias l='/usr/bin/ls -h --color=always --group-directories-first'
+ ialias la='/usr/bin/ls -alh --color=always --group-directories-first'
+ ialias llf='/usr/bin/ls -Fls --color=always --group-directories-first'
+ ialias lla='/usr/bin/ls -aFls --color=always --group-directories-first'
+ ialias lm='/usr/bin/ls -alh --color=always --group-directories-first | more'
+ ialias lr='/usr/bin/ls -hlR --color=always --group-directories-first'
+ ialias ls='/usr/bin/ls -AFh --color=always --group-directories-first'
+ ialias llsa='/usr/bin/ls -hlru --color=always --group-directories-first'
+ ialias llsc='/usr/bin/ls -hclr --color=always --group-directories-first'
+ ialias lld='/usr/bin/ls -l --color=always | grep "^d"'
+ ialias llda='/usr/bin/ls -la --color=always | grep "^d"'
+ ialias llse='/usr/bin/ls -BhlX --color=always --group-directories-first'
+ ialias llsf='/usr/bin/ls -l --color=always | grep -v "^d"'
+ ialias llsfa='/usr/bin/ls -la --color=always | grep -v "^d"'
+ ialias llsm='/usr/bin/ls -hlr --time=mtime --color=always --group-directories-first'
+ ialias llsn='/usr/bin/ls -alp --color=always --group-directories-first'
+ ialias llss='/usr/bin/ls -hlrS --color=always --group-directories-first'
+ ialias llst='/usr/bin/ls -hlrt --color=always --group-directories-first'
+ ialias lw='/usr/bin/ls -Ahx --color=always --group-directories-first'
+}
+
+# fastfetch
+alias ff='fastfetch'
+
+# fcitx5-remote
+alias fr='fcitx5-remote'
+
+# ffmpeg
+alias ffmpeg='ffmpeg -hide_banner'
+
+# find
+balias fdn='find . -name "'
+
+# git
+alias lg="lazygit"
+alias gu="gitupdate"
+alias ggg="ssh $THESIAH_GIT"
+alias gis="githubissuesync"
+
+# greb
+ialias -g grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}'
+alias grepi='grep -i'
+alias grepr='grep -r'
+alias grepri='grep -ri'
+alias grepw='grep -R -i --include="*"'
+alias grepb='grep -R -i --include="*" --exclude-dir="zsh"'
+alias -g G='| grep'
+alias -g Gi='| grep -i'
+alias -g GH='| grep HTTP'
+
+# hash
+alias h='hash -rf'
+
+# hexdump
+alias hx='hexdump -C'
+
+# hugo
+alias hss='hugo server --noHTTPCache'
+alias hcl='hugo --cleanDestinationDir'
+
+# ip
+ialias ip='ip -color=auto'
+alias whatsmyip='curl -s ifconfig.me | xargs'
+
+# jupyter
+alias ji='git clone git@github.com:jupyter/jupyter_client.git'
+alias jn='jupyter notebook'
+
+# killall
+alias ka='killall'
+alias k9='kill -9'
+alias k15='kill -15'
+
+# lf
+alias lf='lfub'
+
+# mime
+alias mimereset="update-desktop-database ${XDG_DATA_HOME:-${HOME}/.local/share}/applications"
+
+# mkdir
+ialias mkdir='mkdir -pv'
+
+# move
+ialias mv='mv -iv'
+
+# mysql
+alias msr='mysql -u si -p'
+
+# network
+alias wi='sudo wifi-menu'
+alias p1='ping 1.1.1.1'
+alias p192='ping 192.168.0.1'
+alias p8='ping 8.8.8.8'
+alias p9='ping 9.9.9.9'
+
+# nvim
+alias v="$EDITOR"
+alias v.="$EDITOR ."
+alias ve="$EDITOR -c enew"
+alias nv.='nvim .'
+alias nve='nvim -c enew'
+alias nts='NVIM_APPNAME=TheSiahxyz nvim'
+alias nlv='NVIM_APPNAME=LazyVim nvim'
+alias nnc='NVIM_APPNAME=NvChad nvim'
+alias snv='sudo nvim'
+alias vll='lastnvim -l'
+alias vln='$EDITOR -c '\''execute "edit " . v:oldfiles[0] | normal ''0'\'
+
+# nxsiv
+alias nsxiv='nsxiv -p'
+
+# obsidian
+alias vo="cd $HOME/Obsidian/SI && nvim Dashboard.md"
+
+# open
+alias open='xdg-open'
+
+# pacman
+command -v pacman > /dev/null 2>&1 && {
+ alias -g pcy='pacman -Syu'
+ alias -g pcyr='pacman -Syu && remaps'
+ alias -g pcs='pacman -S'
+ alias -g pcss='pacman -Ss'
+ alias -g pcqs='pacman -Qs'
+ alias -g pcr='pacman -R'
+ alias -g pcrs='pacman -Rs'
+ alias -g pcclean='pacman -Rsn $(pacman -Qqdt)'
+}
+
+# pass
+alias potp='pass otp totp-secret'
+
+# ps
+ialias ps='ps auxf'
+alias psj='ps aux | grep "[j]ava"'
+balias psg='ps auxf | grep'
+alias topcpu='/bin/ps -eo pcpu,pid,user,args | sort -k 1 -r | head -10'
+
+# python
+alias py='python3'
+
+# realpath
+alias rp='realpath'
+
+# remove
+ialias rm='rm -vI'
+
+# rsync
+alias rsc='rsync -vrazPlu'
+alias rscd='rsync -vrazPlu --delete'
+alias rscr='rsync -vrazPlu --remove-source-files'
+
+# rules
+alias rrr='sudo udevadm control --reload-rules'
+
+# samba
+alias winip="sudo mount -t cifs //192.168.0.1/HDD1 /media/$USER/hdd -o username=si,vers=2.0"
+alias win10="sudo mount -t cifs //192.168.0.3/Share /media/$USER/win10 -o username=THESIAHXYZ-WIN1"
+
+# sc-im
+alias scim='sc-im'
+
+# scp
+ialias scp='scp -r'
+
+# scripts
+alias cs='createscript'
+alias tmzs='timezones'
+
+# setxkbmap
+alias RM='setxkbmap -option'
+
+# sha1
+alias sha1='openssl sha1'
+
+# shell
+alias tobash="sudo chsh $USER -s /bin/bash && 'Now log out.'"
+alias tozsh="sudo chsh $USER -s /bin/zsh && 'Now log out.'"
+alias tofish="sudo chsh $USER -s /bin/fish && 'Now log out.'"
+
+# shellcheck
+alias shck='shellcheck --color=always'
+
+# shortcut
+alias ref="shortcuts >/dev/null; source ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutrc ; source ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutenvrc ; source ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/zshnameddirrc"
+
+# skype
+alias skype='skypeforlinux'
+
+# ssh
+alias gts="ssh $THESIAH_SERVER"
+
+# sudo
+alias su='sudo su -l root'
+alias smi='setopt no_nomatch && rm -rf *.rej *.orig >/dev/null 2>&1 && unsetopt no_nomatch; sudo make clean install'
+
+# suffix
+alias -s {pdf,PDF}='background mupdf'
+alias -s {jpg,JPG,png,PNG}='background gpicview'
+alias -s {ods,ODS,odt,ODT,odp,ODP,doc,DOC,docx,DOCX,xls,XLS,xlsx,XLSX,xlsm,XLSM,ppt,PPT,pptx,PPTX,csv,CSV}='background libreoffice'
+alias -s {html,HTML}='background chromium'
+alias -s {mp4,MP4,mov,MOV,mkv,MKV}='background vlc'
+alias -s {zip,ZIP,war,WAR}="unzip -l"
+alias -s {jar,JAR}="java -jar"
+alias -s gz="tar -tf"
+alias -s {tgz,TGZ}="tar -tf"
+
+# tar
+alias txf='tar -xf'
+alias ttf='tar -tf'
+
+# task
+alias tsc='task-switch-context'
+alias twt='taskwarrior-tui'
+alias tasksync='task sync && command pkill -RTMIN+25 ${STATUSBAR:-dwmblocks} && task'
+
+# tmux
+alias sts='tmux source $XDG_CONFIG_HOME/tmux/tmux.conf'
+alias ta='tmux a'
+alias tmc='tmuxcreate'
+alias tmka='tmux kill-session -a'
+alias tmls='tmux ls'
+alias tmo='tmuxopen'
+alias tmpk='command pkill tmux'
+alias tm.='tmux new -s "$(basename $PWD)"'
+
+# transmission-remote
+alias trem='transmission-remote'
+
+# trash
+alias trd='trash-rm'
+alias tre='trash-empty'
+alias trl='trash-list'
+alias trp='trash-put'
+alias trr='trash-restore'
+
+# tree
+ialias tree='tree -a -I ".svn|.git|.hg|.idea"'
+alias tree2='tree -L 2'
+alias tree3='tree -L 3'
+
+# unix
+alias -g md='mkdir -p'
+alias -g wh='which'
+alias -g wt='while true; do'
+alias -g s1='sleep 1'
+alias -g s2='sleep 2'
+alias -g s01='sleep 0.1'
+alias -g s05='sleep 0.5'
+alias -g A1="| awk '{print \$1}'"
+alias -g L='| less'
+alias -g H='| head'
+alias -g H2='| head -n 20'
+alias -g X='| xargs -I@'
+alias -g C='| xclip -selection clipboard'
+alias -g Fj='| jq .'
+alias -g Fy='| yq .'
+alias -g Fx='| xmllint --format -'
+alias -g V='| nvim -'
+
+# unzip
+alias uz='unzip'
+alias uzl='unzip -l'
+
+# vim
+alias vi='vim'
+alias vi.='vim .'
+
+# watch
+alias w1='watch -n 1'
+
+# wget
+ialias wget --hsts-file="${XDG_CACHE_HOME:-${HOME}/.cache}/wget-hsts"
+
+# xrandr
+alias xauto='xrandr --auto'
+
+# youtube-viewer
+alias yt='youtube-viewer'
+
+# zathura
+alias za='zathura'
+
+# zsh
+alias sps="source ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/profile"
+alias szs="source ${XDG_CONFIG_HOME:-${HOME}/.config}/zsh/.zshrc"
diff --git a/ar/.config/shell/bm-dirs b/ar/.config/shell/bm-dirs
new file mode 100644
index 0000000..9da1143
--- /dev/null
+++ b/ar/.config/shell/bm-dirs
@@ -0,0 +1,108 @@
+# Keys Filename
+bb ${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}
+bs ${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/statusbar
+btp ${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/tmux
+btw ${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/task/taskwarrior-tui
+bz ${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/zsh
+cab ${XDG_CONFIG_HOME:-${HOME}/.config}/abook
+cac ${XDG_CACHE_HOME:-${HOME}/.cache}
+cbc ${XDG_CONFIG_HOME:-${HOME}/.config}/bash
+cdb ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwmblocks
+cdm ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dmenu
+cdw ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwm
+cff ${XDG_CONFIG_HOME:-${HOME}/.config}/fastfetch
+cfg ${XDG_CONFIG_HOME:-${HOME}/.config}
+cfx ${XDG_CONFIG_HOME:-${HOME}/.config}/firefox
+cgc ${XDG_CONFIG_HOME:-${HOME}/.config}/git
+clf ${XDG_CONFIG_HOME:-${HOME}/.config}/lf
+clg ${XDG_CONFIG_HOME:-${HOME}/.config}/lazygit
+clv ${XDG_CONFIG_HOME:-${HOME}/.config}/LazyVim
+cmd ${XDG_CONFIG_HOME:-${HOME}/.config}/mpd
+cms ${XDG_CONFIG_HOME:-${HOME}/.config}/mpv/scripts
+cmv ${XDG_CONFIG_HOME:-${HOME}/.config}/mpv
+cmt ${XDG_CONFIG_HOME:-${HOME}/.config}/mutt
+cnc ${XDG_CONFIG_HOME:-${HOME}/.config}/NvChad
+cnp ${XDG_CONFIG_HOME:-${HOME}/.config}/ncmpcpp
+cns ${XDG_CONFIG_HOME:-${HOME}/.config}/newsboat
+cnv ${XDG_CONFIG_HOME:-${HOME}/.config}/nvim/lua/thesiahxyz
+cr ${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/cron
+cse ${XDG_CONFIG_HOME:-${HOME}/.config}/sesh
+csh ${XDG_CONFIG_HOME:-${HOME}/.config}/shell
+csl ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/slock
+cst ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/st
+ctc ${XDG_CONFIG_HOME:-${HOME}/.config}/task
+ctd ${XDG_CONFIG_HOME:-${HOME}/.config}/transmission-daemon
+ctm ${XDG_CONFIG_HOME:-${HOME}/.config}/tmux
+cts ${XDG_CONFIG_HOME:-${HOME}/.config}/TheSiahxyz/lua/thesiahxyz
+cvc ${XDG_CONFIG_HOME:-${HOME}/.config}/vim
+cvp ${XDG_CONFIG_HOME:-${HOME}/.config}/openvpn
+cxc ${XDG_CONFIG_HOME:-${HOME}/.config}/x11
+cyv ${XDG_CONFIG_HOME:-${HOME}/.config}/youtube-viewer
+czc ${XDG_CONFIG_HOME:-${HOME}/.config}/zsh
+dot ${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}
+Esm /etc/samba
+Esv /etc/runit/sv
+gdc ${XDG_DOCUMENTS_DIR:-${HOME}/Documents}
+gdk ${XDG_DESKTOP_DIR:-${HOME}/Desktop}
+gdn ${XDG_DOWNLOAD_DIR:-${HOME}/Downloads}
+gfx $HOME/.mozilla/firefox/si.default
+gkd ${XDG_DATA_HOME:-${HOME}/.local/share}/kakaotalk/drive_c/users/si/Documents/KakaoTalk-Downloads
+gmu ${XDG_MUSIC_DIR:-${HOME}/Music}
+gpb ${XDG_PUBLICSHARE_DIR:-${HOME}/Public}
+gpp ${XDG_PICTURES_DIR:-${HOME}/Pictures}
+gpv $HOME/Private
+grr ${XDG_PICTURES_DIR:-${HOME}/Pictures}/resources
+gss ${XDG_PICTURES_DIR:-${HOME}/Pictures}/screenshots
+gtc $HOME/Torrents/complete
+gti $HOME/Torrents/incomplete
+gtt $HOME/Torrents
+gvv ${XDG_VIDEOS_DIR:-${HOME}/Videos}
+gvy ${XDG_VIDEOS_DIR:-${HOME}/Videos}/Youtube
+gwf $HOME/.librewolf/si.default
+gwo ${XDG_DESKTOP_DIR:-${HOME}/Desktop}/Work
+gww ${XDG_PICTURES_DIR:-${HOME}/Pictures}/wallpaper
+Hbk /mnt/second/backup
+Hdd /media/$USER/HDD
+Hfl /media/$USER/flash
+Hme /media/$USER
+Hmt /mnt
+Hmw /usr/local/share/mutt-wizard
+Hrr /run/runit/service
+Hsc /mnt/second
+Hsg /media/$USER/storage/G
+Hsm /media/$USER/samba
+Hss /media/$USER/ssd
+Hst /media/$USER/storage
+Hvt /media/$USER/Ventoy
+Hvv /mnt/second/videos
+Hwv /media/$USER/win10/Videos
+pae ${PASSWORD_STORE_DIR:-$XDG_DATA_HOME/.password-store}/exported_keys
+pah ${PASSWORD_STORE_DIR:-$XDG_DATA_HOME/.password-store}/ssh
+pas ${PASSWORD_STORE_DIR:-$XDG_DATA_HOME/.password-store}
+pbg ${XDG_PUBLICSHARE_DIR:-${HOME}/Public}/repos
+pvg $HOME/Private/repos
+pvm $HOME/Private/repos/mutt-wizard
+pvn $HOME/Private/repos/neetcode
+pvo $HOME/Private/repos/Obsidian/SI
+pvs $HOME/Private/repos/SQL
+pww $HOME/Private/photo
+shh ${XDG_DATA_HOME:-${HOME}/.local/share}/history
+shg ${XDG_DATA_HOME:-${HOME}/.local/share}/nvim/gp/chats
+shm ${XDG_DATA_HOME:-${HOME}/.local/share}/mail
+shp ${XDG_DATA_HOME:-${HOME}/.local/share}/wallpapers
+shr ${XDG_DATA_HOME:-${HOME}/.local/share}
+sht ${XDG_DATA_HOME:-${HOME}/.local/share}/task
+shv ${XDG_DATA_HOME:-${HOME}/.local/share}/venvs
+shw ${XDG_DATA_HOME:-${HOME}/.local/share}/wine
+sk ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless
+slr ${XDG_DATA_HOME:-${HOME}/.local/share}/lyrics
+sr ${XDG_SOURCES_HOME:-${HOME}/.local/src}
+tk ${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/keys
+tr ${XDG_DATA_HOME:-${HOME}/.local/share}/Trash/files
+ts ${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah
+tt ${XDG_STATE_HOME:-${HOME}/.local/state}
+vwk ${XDG_DATA_HOME:-${HOME}/.local/share}/vimwiki
+vww $HOME/.wallpapers/video
+web ${THESIAH_WWW:-${HOME}/Private/repos/THESIAH}
+wep ${THESIAH_WWW:-${HOME}/Private/repos/THESIAH}/public
+wes ${THESIAH_WWW:-${HOME}/Private/repos/THESIAH}/static
diff --git a/ar/.config/shell/bm-files b/ar/.config/shell/bm-files
new file mode 100644
index 0000000..9e1f337
--- /dev/null
+++ b/ar/.config/shell/bm-files
@@ -0,0 +1,51 @@
+# Keys Filename Description
+vab ${XDG_CONFIG_HOME:-${HOME}/.config}/abook/addressbook # Address book config
+vbc ${XDG_CONFIG_HOME:-${HOME}/.config}/bash/bashrc # Bash (shell) config
+vbd ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/bm-dirs # A list of bookmarked directories similar to this file
+vbf ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/bm-files # This file, a list of bookmarked files
+vbi ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/inputrc # This file is for gnu readline
+vbp ${XDG_CONFIG_HOME:-${HOME}/.config}/bash/bash_profile # Bash profile
+vcr ${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/$(whereami)/.config/crons # Cron job list
+vdb ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwmblocks/config.h # Dwmblocks: the status bar for dwm
+vdm ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dmenu/config.h # Dmenu: a dynamic menu
+vdw ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwm/config.h # Dwm: a dynamic window manager
+vff ${XDG_CONFIG_HOME:-${HOME}/.config}/fastfetch/config.jsonc # Fastfetch config
+vga ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/git-aliasrc # Git aliases
+vgc ${XDG_CONFIG_HOME:-${HOME}/.config}/git/config # Git config
+vgi ${XDG_CONFIG_HOME:-${HOME}/.config}/git/ignore # Git ignore
+vlf ${XDG_CONFIG_HOME:-${HOME}/.config}/lf/lfrc # LF (file browser) config
+vlg ${XDG_CONFIG_HOME:-${HOME}/.config}/lazygit/config.yml # Lazygit config
+vls ${XDG_CONFIG_HOME:-${HOME}/.config}/lf/scope # LF's scope/preview file
+vly $HOME/.lynxrc # Lynx config
+vmi ${XDG_CONFIG_HOME:-${HOME}/.config}/mpv/input.conf # Mpv input config
+vmc ${XDG_CONFIG_HOME:-${HOME}/.config}/mpv/mpv.conf # Mpv config
+vmt ${XDG_CONFIG_HOME:-${HOME}/.config}/mutt/muttrc # Mutt (email client) config
+vnb ${XDG_CONFIG_HOME:-${HOME}/.config}/ncmpcpp/bindings # Ncmpcpp (music player) keybinds file
+vnp ${XDG_CONFIG_HOME:-${HOME}/.config}/ncmpcpp/config # Ncmpcpp (music player) config
+vns ${XDG_CONFIG_HOME:-${HOME}/.config}/newsboat/config # Newsboat config (RSS reader)
+vnu ${XDG_CONFIG_HOME:-${HOME}/.config}/newsboat/urls # Newsboat url (RSS reader)
+Vsm /etc/samba/smb.conf # Samba config
+vse ${XDG_CONFIG_HOME:-${HOME}/.config}/sesh/sesh.toml # Sesh config
+vsl ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/slock/config.h # Slock: lock screen
+vsp ${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/snippets # Snippets
+vss ${XDG_CONFIG_HOME:-${HOME}/.config}/starship/starship.toml # Starship
+vst ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/st/config.h # St: a simple terminal
+vsx ${XDG_CONFIG_HOME:-${HOME}/.config}/nsxiv/exec/key-handler # Nsxiv (image viewer) key/script handler
+vtc ${XDG_CONFIG_HOME:-${HOME}/.config}/task/taskrc # Task config
+vtm ${XDG_CONFIG_HOME:-${HOME}/.config}/tmux/tmux.conf # Tmux config
+vto ${XDG_CONFIG_HOME:-${HOME}/.config}/taskopen/taskopenrc # Taskopen config
+vtt ${XDG_CONFIG_HOME:-${HOME}/.config}/transmission-daemon/settings.json # Transmission-daemon config
+vur ${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/urls # Custom urls
+vvc ${XDG_CONFIG_HOME:-${HOME}/.config}/vim/vimrc # Vim config
+vvi ${XDG_CONFIG_HOME:-${HOME}/.config}/vim/init.vim # Vim init
+vvp ${XDG_CONFIG_HOME:-${HOME}/.config}/vim/plugins.vim # Vim plugins
+vvw ${XDG_DATA_HOME:-${HOME}/.local/share}/vimwiki # Vimwiki
+vxc ${XDG_CONFIG_HOME:-${HOME}/.config}/x11/xinitrc # X11 config
+vxp ${XDG_CONFIG_HOME:-${HOME}/.config}/x11/xprofile # Start up - X11 profile
+vxr ${XDG_CONFIG_HOME:-${HOME}/.config}/x11/xresources # Colors, themes and variables for X11
+vya ${XDG_CONFIG_HOME:-${HOME}/.config}/youtube-viewer/api.json # API config for youtube-viewer
+vza ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/aliasrc # Aliases used by zsh (and potentially other shells)
+vzc ${ZDOTDIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/zsh}/.zshrc # Zsh config
+vzk ${ZDOTDIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/zsh}/keymaps.zsh # Zsh keymaps
+vzp ${XDG_CONFIG_HOME:-${HOME}/.config}/shell/profile # Zsh profile used for system
+vzs ${ZDOTDIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/zsh}/scripts.zsh # Zsh scripts
diff --git a/ar/.config/shell/git-aliasrc b/ar/.config/shell/git-aliasrc
new file mode 100644
index 0000000..f87a556
--- /dev/null
+++ b/ar/.config/shell/git-aliasrc
@@ -0,0 +1,415 @@
+# Git version checking
+autoload -Uz is-at-least
+git_version="${${(As: :)$(git version 2>/dev/null)}[3]}"
+
+#
+# Functions Current
+# (sorted alphabetically by function name)
+# (order should follow README)
+#
+
+# The name of the current branch
+# Back-compatibility wrapper for when this function was defined here in
+# the plugin, before being pulled in to core lib/git.zsh as git_current_branch()
+# to fix the core -> git plugin dependency.
+current_branch() {
+ git_current_branch
+}
+
+# Check for develop and similarly named branches
+git_develop_branch() {
+ command git rev-parse --git-dir &>/dev/null || return
+ local branch
+ for branch in dev devel develop development; do
+ if command git show-ref -q --verify refs/heads/$branch; then
+ echo $branch
+ return 0
+ fi
+ done
+
+ echo develop
+ return 1
+}
+
+# Check if main exists and use instead of master
+git_main_branch() {
+ command git rev-parse --git-dir &>/dev/null || return
+ local ref
+ for ref in refs/{heads,remotes/{origin,upstream}}/{main,trunk,mainline,default,master}; do
+ if command git show-ref -q --verify $ref; then
+ echo ${ref:t}
+ return 0
+ fi
+ done
+
+ # If no main branch was found, fall back to master but return error
+ echo master
+ return 1
+}
+
+grename() {
+ if [[ -z "$1" || -z "$2" ]]; then
+ echo "Usage: $0 old_branch new_branch"
+ return 1
+ fi
+
+ # Rename branch locally
+ git branch -m "$1" "$2"
+ # Rename branch in origin remote
+ if git push origin :"$1"; then
+ git push --set-upstream origin "$2"
+ fi
+}
+
+#
+# Functions Work in Progress (WIP)
+# (sorted alphabetically by function name)
+# (order should follow README)
+#
+
+# Similar to `gunwip` but recursive "Unwips" all recent `--wip--` commits not just the last one
+gunwipall() {
+ local _commit=$(git log --grep='--wip--' --invert-grep --max-count=1 --format=format:%H)
+
+ # Check if a commit without "--wip--" was found and it's not the same as HEAD
+ if [[ "$_commit" != "$(git rev-parse HEAD)" ]]; then
+ git reset $_commit || return 1
+ fi
+}
+
+# Warn if the current branch is a WIP
+work_in_progress() {
+ command git -c log.showSignature=false log -n 1 2>/dev/null | grep -q -- "--wip--" && echo "WIP!!"
+}
+
+#
+# Aliases
+# (sorted alphabetically by command)
+# (order should follow README)
+# (in some cases force the alisas order to match README, like for example gke and gk)
+#
+
+alias grt='cd "$(git rev-parse --show-toplevel || echo .)"'
+
+ggpnp() {
+ if [[ "$#" == 0 ]]; then
+ ggl && ggp
+ else
+ ggl "${*}" && ggp "${*}"
+ fi
+}
+compdef _git ggpnp=git-checkout
+
+alias ggpur='ggu'
+alias g='git'
+! pidof transmission-daemon >/dev/null && alias gaa='git add --all' || alias gaa='echo "Turn off transmission-daemon first!"'
+alias gapa='git add --patch'
+alias gau='git add --update'
+alias gav='git add --verbose'
+alias gwip='git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify --no-gpg-sign --message "--wip-- [skip ci]"'
+alias gam='git am'
+alias gama='git am --abort'
+alias gamc='git am --continue'
+alias gamscp='git am --show-current-patch'
+alias gams='git am --skip'
+alias gap='git apply'
+alias gapt='git apply --3way'
+alias gbs='git bisect'
+alias gbsb='git bisect bad'
+alias gbsg='git bisect good'
+alias gbsn='git bisect new'
+alias gbso='git bisect old'
+alias gbsr='git bisect reset'
+alias gbss='git bisect start'
+alias gb='git branch'
+alias gba='git branch --all'
+alias gbD='git branch --delete --force'
+
+gbda() {
+ git branch --no-color --merged | command grep -vE "^([+*]|\s*($(git_main_branch)|$(git_develop_branch))\s*$)" | command xargs git branch --delete 2>/dev/null
+}
+
+# Copied and modified from James Roeder (jmaroeder) under MIT License
+# https://github.com/jmaroeder/plugin-git/blob/216723ef4f9e8dde399661c39c80bdf73f4076c4/functions/gbda.fish
+gbds() {
+ local default_branch=$(git_main_branch)
+ (( ! $? )) || default_branch=$(git_develop_branch)
+
+ git for-each-ref refs/heads/ "--format=%(refname:short)" | \
+ while read branch; do
+ local merge_base=$(git merge-base $default_branch $branch)
+ if [[ $(git cherry $default_branch $(git commit-tree $(git rev-parse $branch\^{tree}) -p $merge_base -m _)) = -* ]]; then
+ git branch -D $branch
+ fi
+ done
+}
+
+alias gbgd='LANG=C git branch --no-color -vv | grep ": gone\]" | awk '"'"'{print $1}'"'"' | xargs git branch -d'
+alias gbgD='LANG=C git branch --no-color -vv | grep ": gone\]" | awk '"'"'{print $1}'"'"' | xargs git branch -D'
+alias gbm='git branch --move'
+alias gbnm='git branch --no-merged'
+alias gbr='git branch --remote'
+alias ggsup='git branch --set-upstream-to=origin/$(git_current_branch)'
+alias gbg='LANG=C git branch -vv | grep ": gone\]"'
+alias gcor='git checkout --recurse-submodules'
+alias gcB='git checkout -B'
+alias gcd='git checkout $(git_develop_branch)'
+alias gcm='git checkout $(git_main_branch)'
+alias gcpa='git cherry-pick --abort'
+alias gcpc='git cherry-pick --continue'
+alias gcl='git clone --recurse-submodules'
+
+gccd() {
+ setopt localoptions extendedglob
+
+ # get repo URI from args based on valid formats: https://git-scm.com/docs/git-clone#URLS
+ local repo='${${@[(r)(ssh://*|git://*|ftp(s)#://*|http(s)#://*|*@*)(.git/#)#]}:-$_}'
+
+ # clone repository and exit if it fails
+ command git clone --recurse-submodules "$@" || return
+
+ # if last arg passed was a directory, that's where the repo was cloned
+ # otherwise parse the repo URI and use the last part as the directory
+ [[ -d "$_" ]] && cd '$_' || cd '${${repo:t}%.git/#}'
+}
+compdef _git gccd=git-clone
+
+alias gcam='git commit --all --message'
+alias gcas='git commit --all --signoff'
+alias gcasm='git commit --all --signoff --message'
+alias gcs='git commit --gpg-sign'
+alias gcss='git commit --gpg-sign --signoff'
+alias gcssm='git commit --gpg-sign --signoff --message'
+alias gcmsg='git commit --message'
+alias gcsm='git commit --signoff --message'
+alias gc='git commit --verbose'
+alias gca='git commit --verbose --all'
+alias gca!='git commit --verbose --all --amend'
+alias gcan!='git commit --verbose --all --no-edit --amend'
+alias gcans!='git commit --verbose --all --signoff --no-edit --amend'
+alias gc!='git commit --verbose --amend'
+alias gcn!='git commit --verbose --no-edit --amend'
+alias gdct='git describe --tags $(git rev-list --tags --max-count=1)'
+alias gdca='git diff --cached'
+alias gdcw='git diff --cached --word-diff'
+alias gds='git diff --staged'
+alias gdw='git diff --word-diff'
+
+gdv() { git diff -w "$@" | view - }
+compdef _git gdv=git-diff
+
+alias gdup='git diff @{upstream}'
+
+gdnolock() {
+ git diff "$@" ":(exclude)package-lock.json" ":(exclude)*.lock"
+}
+compdef _git gdnolock=git-diff
+
+alias gdt='git diff-tree --no-commit-id --name-only -r'
+alias gf='git fetch'
+# --jobs=<n> was added in git 2.8
+is-at-least 2.8 "$git_version" \
+ && alias gfa='git fetch --all --prune --jobs=10' \
+ || alias gfa='git fetch --all --prune'
+alias gfo='git fetch origin'
+alias gg='git gui citool'
+alias gga='git gui citool --amend'
+alias ghh='git help'
+alias glgg='git log --graph'
+alias glggp='git log --graph --parents'
+alias glgga='git log --graph --decorate --all'
+alias glggpa='git log --graph --decorate --parents --all'
+alias glgm='git log --graph --max-count=10'
+alias glgpm='git log --graph --parents --max-count=10'
+alias gloac='git log --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%ae>%Creset" --abbrev-commit --all'
+alias glods='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset" --date=short'
+alias glopds='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset" --parents --date=short'
+alias glod='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset"'
+alias glopd='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset" --parents'
+alias glola='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --all'
+alias glolpa='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --parents --all'
+alias glols='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --stat'
+alias glolps='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --parents --stat'
+alias glol='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset"'
+alias glolp='git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --parents'
+alias glog='git log --oneline --decorate --graph'
+alias glogp='git log --oneline --decorate --graph --parents'
+alias gloga='git log --oneline --decorate --graph --all'
+alias glogpa='git log --oneline --decorate --graph --parents --all'
+
+# Pretty log messages
+_git_log_prettily(){
+ if ! [ -z $1 ]; then
+ git log --pretty=$1
+ fi
+}
+compdef _git _git_log_prettily=git-log
+
+alias glp='_git_log_prettily'
+alias glg='git log --stat'
+alias glgp='git log --stat --patch'
+alias gignored='git ls-files -v | grep "^[[:lower:]]"'
+alias gfg='git ls-files | grep'
+alias gm='git merge'
+alias gma='git merge --abort'
+alias gms="git merge --squash"
+alias gmom='git merge origin/$(git_main_branch)'
+alias gmum='git merge upstream/$(git_main_branch)'
+alias gmtl='git mergetool --no-prompt'
+alias gmtlvim='git mergetool --no-prompt --tool=vimdiff'
+
+alias gl='git pull'
+alias gprb='git pull --rebase'
+alias gprbv='git pull --rebase -v'
+alias gpra='git pull --rebase --autostash'
+alias gprav='git pull --rebase --autostash -v'
+
+ggu() {
+ [[ "$#" != 1 ]] && local b="$(git_current_branch)"
+ git pull --rebase origin "${b:=$1}"
+}
+compdef _git ggu=git-checkout
+
+alias gprom='git pull --rebase origin $(git_main_branch)'
+alias gpromi='git pull --rebase=interactive origin $(git_main_branch)'
+alias ggpull='git pull origin "$(git_current_branch)"'
+
+ggl() {
+ if [[ "$#" != 0 ]] && [[ "$#" != 1 ]]; then
+ git pull origin "${*}"
+ else
+ [[ "$#" == 0 ]] && local b="$(git_current_branch)"
+ git pull origin "${b:=$1}"
+ fi
+}
+compdef _git ggl=git-checkout
+
+alias gluc='git pull upstream $(git_current_branch)'
+alias glum='git pull upstream $(git_main_branch)'
+# alias gp='git push'
+alias gpd='git push --dry-run'
+
+ggf() {
+ [[ "$#" != 1 ]] && local b="$(git_current_branch)"
+ git push --force origin "${b:=$1}"
+}
+compdef _git ggf=git-checkout
+
+alias gpf!='git push --force'
+is-at-least 2.30 "$git_version" \
+ && alias gpf='git push --force-with-lease --force-if-includes' \
+ || alias gpf='git push --force-with-lease'
+
+ggfl() {
+ [[ "$#" != 1 ]] && local b="$(git_current_branch)"
+ git push --force-with-lease origin "${b:=$1}"
+}
+compdef _git ggfl=git-checkout
+
+alias gpsup='git push --set-upstream origin $(git_current_branch)'
+is-at-least 2.30 "$git_version" \
+ && alias gpsupf='git push --set-upstream origin $(git_current_branch) --force-with-lease --force-if-includes' \
+ || alias gpsupf='git push --set-upstream origin $(git_current_branch) --force-with-lease'
+alias gpvb='git push --verbose'
+alias gpoat='git push origin --all && git push origin --tags'
+alias gpod='git push origin --delete'
+alias ggpush='git push origin "$(git_current_branch)"'
+
+ggp() {
+ if [[ "$#" != 0 ]] && [[ "$#" != 1 ]]; then
+ git push origin "${*}"
+ else
+ [[ "$#" == 0 ]] && local b="$(git_current_branch)"
+ git push origin "${b:=$1}"
+ fi
+}
+compdef _git ggp=git-checkout
+
+alias gpu='git push upstream'
+alias grba='git rebase --abort'
+alias grbc='git rebase --continue'
+alias grbi='git rebase --interactive'
+alias grbo='git rebase --onto'
+alias grbs='git rebase --skip'
+alias grbd='git rebase $(git_develop_branch)'
+alias grbm='git rebase $(git_main_branch)'
+alias grbom='git rebase origin/$(git_main_branch)'
+alias gr='git remote'
+alias grv='git remote --verbose'
+alias gra='git remote add'
+alias grrm='git remote remove'
+alias grmv='git remote rename'
+alias grset='git remote set-url'
+alias grup='git remote update'
+alias gru='git reset --'
+alias grhh='git reset --hard'
+alias grhk='git reset --keep'
+alias grhs='git reset --soft'
+alias gpristine='git reset --hard && git clean --force -dfx'
+alias groh='git reset origin/$(git_current_branch) --hard'
+alias grs='git restore'
+alias grss='git restore --source'
+alias grst='git restore --staged'
+alias gunwip='git rev-list --max-count=1 --format="%s" HEAD | grep -q "\--wip--" && git reset HEAD~1'
+alias grev='git revert'
+alias grm='git rm'
+alias grmc='git rm --cached'
+alias gcount='git shortlog --summary --numbered'
+alias gsh='git show'
+alias gsps='git show --pretty=short --show-signature'
+alias gstall='git stash --all'
+alias gstaa='git stash apply'
+alias gstc='git stash clear'
+alias gstd='git stash drop'
+alias gstl='git stash list'
+alias gstp='git stash pop'
+# use the default stash push on git 2.13 and newer
+is-at-least 2.13 "$git_version" \
+ && alias gsta='git stash push' \
+ || alias gsta='git stash save'
+alias gsts='git stash show --patch'
+alias gst='git status'
+alias gsb='git status --short --branch'
+alias gsi='git submodule init'
+alias gsu='git submodule update'
+alias gsd='git svn dcommit'
+alias git-svn-dcommit-push='git svn dcommit && git push github $(git_main_branch):svntrunk'
+alias gsr='git svn rebase'
+alias gsw='git switch'
+alias gswc='git switch --create'
+alias gswd='git switch $(git_develop_branch)'
+alias gswm='git switch $(git_main_branch)'
+alias gtan='git tag --annotate'
+alias gtsn='git tag --sign'
+alias gtv='git tag | sort -V'
+alias gignore='git update-index --assume-unchanged'
+alias gunignore='git update-index --no-assume-unchanged'
+alias gwch='git whatchanged -p --abbrev-commit --pretty=medium'
+alias gwt='git worktree'
+alias gwta='git worktree add'
+alias gwtls='git worktree list'
+alias gwtmv='git worktree move'
+alias gwtrm='git worktree remove'
+alias gstu='gsta --include-untracked'
+alias gtl='gtl(){ git tag --sort=-v:refname -n --list "${1}*" }; noglob gtl'
+alias gk='\gitk --all --branches &!'
+alias gke='\gitk --all $(git log --walk-reflogs --pretty=%h) &!'
+
+unset git_version
+
+# Logic for adding warnings on deprecated aliases
+local old_alias new_alias
+for old_alias new_alias (
+ # TODO(2023-10-19): remove deprecated `git pull --rebase` aliases
+ gup gpr
+ gupv gprv
+ gupa gpra
+ gupav gprav
+ gupom gprom
+ gupomi gpromi
+); do
+ aliases[$old_alias]='
+ print -Pu2 \"%F{yellow}[oh-my-zsh] '%F{red}${old_alias}%F{yellow}' is a deprecated alias, using '%F{green}${new_alias}%F{yellow}' instead.%f\"
+ $new_alias'
+done
+unset old_alias new_alias
diff --git a/ar/.config/shell/inputrc b/ar/.config/shell/inputrc
new file mode 100644
index 0000000..81cdf85
--- /dev/null
+++ b/ar/.config/shell/inputrc
@@ -0,0 +1,31 @@
+$include /etc/inputrc
+
+set completion-display-width 0
+set completion-query-items 1000
+
+# Prettyfi
+set colored-stats on
+set colored-completion-prefix on
+
+# ^C no longer shows on C-c keypress
+set echo-control-characters off
+
+# Map tab to cycle through all the possible completions.
+TAB: menu-complete
+
+# vi mode
+set editing-mode vi
+
+$if mode=vi
+set show-mode-in-prompt on
+set vi-ins-mode-string \1\e[6 q\2
+set vi-cmd-mode-string \1\e[2 q\2
+set keymap vi-command
+
+# these are for vi-command mode
+Control-l: clear-screen
+
+set keymap vi-insert
+# these are for vi-insert mode
+Control-l: clear-screen
+$endif
diff --git a/ar/.config/shell/profile b/ar/.config/shell/profile
new file mode 100644
index 0000000..9f1d527
--- /dev/null
+++ b/ar/.config/shell/profile
@@ -0,0 +1,231 @@
+###################################################
+### --- PROFILE --- ###
+###################################################
+[ "$(tty)" = "/dev/tty1" ] && set -e # Exit immediately if a command exits with a non-zero status.
+
+###################################################
+### --- ENV PATH --- ###
+###################################################
+# Add all directories in each subdirectory to $PATH
+export PATH="$PATH:$(find ~/.local/bin -path '*/.git*' -prune -o \( -type d -o -type l \) -print | paste -sd ':' -)"
+export PATH="$PATH:$(find ~/.local/share/.password-store -type d -name '.extensions' | paste -sd ':' -)"
+
+unsetopt PROMPT_SP 2>/dev/null
+
+###################################################
+### --- DEFAULT PROGRAMS --- ###
+###################################################
+export BROWSER="firefox"
+export EDITOR="nvim"
+export EDITOR2="vim"
+export KEYTIMEOUT=10
+export SUDO_EDITOR=$EDITOR
+export SUDO_ASKPASS="$HOME/.local/bin/dmenupass"
+export TERMINAL="st"
+export TERMINAL_PROG="st"
+export TERM="st-256color"
+export VISUAL=$EDITOR
+
+###################################################
+### --- XDG ENV PATHES --- ###
+###################################################
+### --- XDG DEFAULT --- ###
+export XDG_CACHE_HOME="$HOME/.cache"
+export XDG_CONFIG_HOME="$HOME/.config"
+export XDG_DATA_HOME="$HOME/.local/share"
+export XDG_STATE_HOME="$HOME/.local/state"
+
+### --- XDG CUSTOMS --- ###
+export XDG_DOTFILES_DIR="$HOME/.dotfiles"
+export XDG_SCRIPTS_HOME="$HOME/.local/bin"
+export XDG_SOURCES_HOME="$HOME/.local/src"
+export XDG_DESKTOP_DIR="$HOME/Desktop"
+export XDG_DOCUMENTS_DIR="$HOME/Documents"
+export XDG_DOWNLOAD_DIR="$HOME/Downloads"
+export XDG_MUSIC_DIR="$HOME/Music"
+export XDG_PICTURES_DIR="$HOME/Pictures"
+export XDG_PUBLICSHARE_DIR="$HOME/Public"
+export XDG_TEMPLATES_DIR="$HOME/Templates"
+export XDG_VIDEOS_DIR="$HOME/Videos"
+
+###################################################
+### --- DEFAULT ENV PATHES FOR ALL PROGRAMS --- ###
+###################################################
+### --- ANDROID --- ###
+export ANDROID_SDK_HOME="$XDG_CONFIG_HOME/android"
+
+### --- ANSIBLE --- ###
+export ANSIBLE_CONFIG="$XDG_CONFIG_HOME/ansible/ansible.cfg"
+
+### --- BAT --- ###
+export BAT_CONFIG_PATH="$XDG_CONFIG_HOME/bat/config"
+
+### --- CARGO --- ###
+export CARGO_HOME="$XDG_DATA_HOME/cargo"
+
+### --- DICS --- ###
+export DICS="/usr/share/stardict/dic/"
+
+### --- ELECTRUM --- ###
+export ELECTRUMDIR="$XDG_DATA_HOME/electrum"
+
+### --- FCITX5 --- ###
+export GTK_IM_MODULE=fcitx
+export QT_IM_MODULE=fcitx
+export XMODIFIERS=@im=fcitx
+
+### --- FZF --- ###
+export FZF_ALT_C_COMMAND="fd --type d . --color=never --hidden"
+export FZF_ALT_C_OPTS="--preview 'tree -C {} | head -50'"
+export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
+export FZF_CTRL_T_OPTS="--preview 'bat --color=always --line-range :50 {}'"
+# export FZF_DEFAULT_OPTS="--no-height --color=bg+:#343d46,gutter:-1,pointer:#ff3c3c,info:#0dbc79,hl:#0dbc79,hl+:#23d18b"
+export FZF_DEFAULT_OPTS="--reverse --height 60% --color=bg+:#343d46,gutter:-1,pointer:#ff3c3c,info:#0dbc79,hl:#0dbc79,hl+:#23d18b"
+
+### --- FORGIT --- ###
+export FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS='--sort=-committerdate'
+export FORGIT_COPY_CMD='xclip -selection clipboard'
+
+### --- GEM --- ###
+export GEMRC="$HOME/.config/gem/gemrc"
+export GEM_HOME="$XDG_DATA_HOME/gem"
+export GEM_PATH="$GEM_HOME"
+
+### --- GO --- ###
+export GOMODCACHE="$XDG_CACHE_HOME/go/mod"
+export GOPATH="$XDG_SCRIPTS_HOME/go"
+
+### --- GTK --- ###
+export GTK2_RC_FILES="$XDG_CONFIG_HOME/gtk-2.0/gtkrc-2.0"
+
+### --- HISTORY --- ###
+export HISTFILE="$XDG_DATA_HOME/history/sh_history"
+
+### --- INPUTRC --- ###
+export INPUTRC="$XDG_CONFIG_HOME/shell/inputrc"
+
+### --- JAVA --- ###
+export AWT_TOOLKIT="MToolkit wmname LG3D" # May have to install wmname
+export _JAVA_AWT_WM_NONREPARENTING=1 # Fix for Java applications in dwm
+
+### --- Jupyter --- ###
+export JUPYTER_CONFIG_DIR="$XDG_CONFIG_HOME/jupyter"
+export JUPYTER_PLATFORM_DIRS="1"
+
+### --- KODI --- ###
+export KODI_DATA="$XDG_DATA_HOME/kodi"
+
+### --- LF --- ###
+export FILE_MANAGER="lf $(lf -version)"
+
+### --- MANPAGER --- ###
+([ -x "$(command -v batcat)" ] || [ -x "$(command -v batman)" ]) && {
+ export MANPAGER="sh -c 'col -bx | bat -l man -p'"
+ export MANROFFOPT="-c"
+} || {
+ export MANPAGER='less -s'
+ export LESS="R"
+ export LESS_TERMCAP_mb="$(printf '%b' '')"
+ export LESS_TERMCAP_md="$(printf '%b' '')"
+ export LESS_TERMCAP_me="$(printf '%b' '')"
+ export LESS_TERMCAP_so="$(printf '%b' '')"
+ export LESS_TERMCAP_se="$(printf '%b' '')"
+ export LESS_TERMCAP_us="$(printf '%b' '')"
+ export LESS_TERMCAP_ue="$(printf '%b' '')"
+ export LESSOPEN="| /usr/bin/highlight -O ansi %s 2>/dev/null"
+}
+
+### --- MBSYNC --- ###
+export MBSYNCRC="$XDG_CONFIG_HOME/mbsync/config"
+
+### --- MOZILLA --- ###
+export MOZ_USE_XINPUT2=1 # Mozilla smooth scrolling/touchpads.
+
+### --- NOTMUCH --- ###
+export NOTMUCH_CONFIG="$XDG_CONFIG_HOME/notmuch/config"
+
+### --- NPM --- ###
+export NPM_CONFIG_USERCONFIG="~/.config/npm/.npmrc"
+
+### --- NVM --- ###
+export NVM_DIR="$XDG_CONFIG_HOME/nvm"
+
+### --- PAM GNUPG --- ###
+# export GNUPGHOME="$XDG_DATA_HOME/gnupg"
+
+### --- PASSWORD STORE --- ###
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/.password-store"
+export PASSWORD_STORE_CLIP_TIME=180 # Specifies the number of seconds to wait before restoring the clipboard, by default 45 seconds.
+# export PASSWORD_STORE_CHARACTER_SET="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*=[]{}|:,.?/"
+export PASSWORD_STORE_ENABLE_EXTENSIONS="true" # This environment variable must be set to "true" for extensions to be enabled.
+# export PASSWORD_STORE_EXTENSIONS_DIR="$PASSWORD_STORE_DIR/.extensions" # The location to look for executable extension files, by default PASSWORD_STORE_DIR/.extensions.
+# export BASH_COMPLETION_USER_DIR=$XDG_DATA_HOME/bash-completion/completions
+
+### --- POWERLEVEL10K --- ###
+export POWERLEVEL9K_INSTALLATION_DIR="/usr/share/zsh-theme-powerlevel10k"
+
+### --- PYTHON --- ###
+export PYTHONPYCACHEPREFIX=$XDG_CACHE_HOME/python
+export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/pythonrc"
+
+### --- QT --- ###
+export QT_QPA_PLATFORMTHEME="gtk2" # Have QT use gtk2 theme.
+
+### --- RUST --- ###
+export RUSTUP_HOME="$XDG_DATA_HOME/rustup"
+
+### --- SERVER --- ###
+export THESIAH="thesiah.xyz"
+export THESIAH_GIT="git@$THESIAH"
+export THESIAH_SERVER="root@$THESIAH"
+export THESIAH_VPN="${THESIAH%%.*}"
+export THESIAH_WWW="$HOME/Private/repos/THESIAH"
+
+### --- SQLITE --- ###
+export SQLITE_HISTORY="$XDG_DATA_HOME/history/sqlite_history"
+
+### --- STARSHIP --- ###
+export STARSHIP_CONFIG="$XDG_CONFIG_HOME/starship/starship.toml"
+
+### --- TMUX --- ###
+export TMUX_TMPDIR="$XDG_RUNTIME_DIR"
+
+### --- UNISON --- ###
+export UNISON="$XDG_DATA_HOME/unison"
+
+### --- VIM --- ###
+# export GVIMINIT='let $MYGVIMRC = !has("nvim") ? "$XDG_CONFIG_HOME/vim/gvimrc" : "$XDG_CONFIG_HOME/nvim/init.lua" | so $MYGVIMRC'
+# export VIMINIT='let $MYVIMRC = !has("nvim") ? "$XDG_CONFIG_HOME/vim/vimrc" : "$XDG_CONFIG_HOME/nvim/init.lua" | so $MYVIMRC'
+
+### --- VIRTUAL ENVIRONMENT --- ###
+export WORKON_HOME="$XDG_DATA_HOME/venvs"
+
+### --- VISUAL STUDIO CODE --- ###
+export VSCODE_PORTABLE="$XDG_DATA_HOME/vscode"
+
+### --- WINE --- ###
+export WINEPREFIX="$XDG_DATA_HOME/wine"
+export WINEDLLOVERRIDES="winemenubuilder.exe=d"
+
+### --- WGET --- ###
+export WGETRC="$XDG_CONFIG_HOME/wget/wgetrc"
+
+### --- X11 --- ###
+export XINITRC="$XDG_CONFIG_HOME/x11/xinitrc"
+export XSERVERRC="$XDG_CONFIG_HOME/X11/xserverrc"
+
+### --- XAUTH --- ###
+export XAUTHORITY="$XDG_RUNTIME_DIR/Xauthority" # This line will break some DMs.
+
+### --- ZSH --- ###
+export ZDOTDIR="$XDG_CONFIG_HOME/zsh"
+export ZPLUGINDIR="$XDG_SCRIPTS_HOME/zsh"
+
+### --- SHORTCUTS --- ###
+[ ! -f "$XDG_CONFIG_HOME/shell/shortcutrc" ] && setsid -f shortcuts >/dev/null 2>&1
+
+### --- START GRAPHICAL SERVER ON USER'S CURRENT TTY IF NOT ALREADY RUNNING --- ###
+[ "$(tty)" = "/dev/tty1" ] && ! pidof -s Xorg >/dev/null 2>&1 && exec startx "$XINITRC"
+
+### --- LAPTOP KEYMAP --- ###
+sudo -n loadkeys "$XDG_DATA_HOME/thesiah/ttymaps.kmap" 2>/dev/null
diff --git a/ar/.config/starship/starship.toml b/ar/.config/starship/starship.toml
new file mode 100644
index 0000000..a38c6a2
--- /dev/null
+++ b/ar/.config/starship/starship.toml
@@ -0,0 +1,204 @@
+"$schema" = 'https://starship.rs/config-schema.json'
+
+format = """
+[](color_orange)\
+$os\
+$username\
+[](bg:color_yellow fg:color_orange)\
+$directory\
+[](fg:color_yellow bg:color_aqua)\
+$git_branch\
+$git_status\
+$git_commit\
+[](fg:color_aqua bg:color_blue)\
+$c\
+$rust\
+$golang\
+$nodejs\
+$php\
+$java\
+$kotlin\
+$haskell\
+$python\
+[](fg:color_blue bg:color_bg3)\
+$docker_context\
+[](fg:color_bg3 bg:color_bg1)\
+$battery\
+$time\
+[ ](fg:color_bg1)\
+$line_break$character"""
+
+palette = 'gruvbox_dark'
+
+[palettes.gruvbox_dark]
+color_fg0 = '#fbf1c7'
+color_bg1 = '#3c3836'
+color_bg3 = '#665c54'
+color_blue = '#458588'
+color_aqua = '#689d6a'
+color_green = '#98971a'
+color_orange = '#d65d0e'
+color_purple = '#b16286'
+color_red = '#cc241d'
+color_yellow = '#d79921'
+
+[os]
+disabled = false
+style = "bg:color_orange fg:color_fg0"
+
+[os.symbols]
+Windows = "󰍲"
+Ubuntu = "󰕈"
+SUSE = ""
+Raspbian = "󰐿"
+Mint = "󰣭"
+Macos = "󰀵"
+Manjaro = ""
+Linux = "󰌽"
+Gentoo = "󰣨"
+Fedora = "󰣛"
+Alpine = ""
+Amazon = ""
+Android = ""
+Arch = "󰣇"
+Artix = "󰣇"
+CentOS = ""
+Debian = "󰣚"
+Redhat = "󱄛"
+RedHatEnterprise = "󱄛"
+
+[username]
+show_always = true
+style_user = "bg:color_orange fg:color_fg0"
+style_root = "bg:color_orange fg:color_fg0"
+format = '[ $user ]($style)'
+
+[directory]
+style = "fg:color_fg0 bg:color_yellow"
+format = "[ $path ]($style)"
+truncation_length = 3
+truncation_symbol = "…/"
+
+[directory.substitutions]
+"Documents" = "󰈙 "
+"Downloads" = " "
+"Music" = "󰝚 "
+"Pictures" = " "
+"Developer" = "󰲋 "
+
+[git_branch]
+symbol = ""
+style = "bg:color_aqua"
+format = '[[ $symbol $branch ](fg:color_fg0 bg:color_aqua)]($style)'
+
+[git_status]
+style = "bg:color_aqua"
+format = '[[($all_status$ahead_behind )](fg:color_fg0 bg:color_aqua)]($style)'
+ahead = " ⇡${count} "
+behind = " ⇣${count} "
+conflicted = "⚔️"
+deleted = "🗑️${count} "
+diverged = "🔱⇡${ahead_count} ⇣${behind_count} "
+modified = "📝${count} "
+renamed = "📛${count} "
+staged = "🗃️${count} "
+stashed = " 📦${count} "
+untracked = "🤷${count} "
+
+[git_commit]
+commit_hash_length = 8
+style = "bg:color_aqua"
+tag_symbol = "🔖 "
+disabled = false
+
+[nodejs]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[c]
+symbol = " "
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[rust]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[golang]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[php]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[java]
+symbol = " "
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[kotlin]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[haskell]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[python]
+symbol = ""
+style = "bg:color_blue"
+format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
+
+[docker_context]
+symbol = ""
+style = "bg:color_bg3"
+format = '[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)'
+
+[battery]
+full_symbol = "🔋"
+charging_symbol = "🔌"
+discharging_symbol = "🪫"
+# format = '[[$symbol$percentage](fg:color_fg0 bg:color_bg1)]($style)'
+# format = '[[ $symbol](fg:color_fg0 bg:color_bg1)]($style)'
+format = '[ $symbol]($style)'
+
+[[battery.display]] # "bold red" style when capacity is between 0% and 10%
+threshold = 25
+style = "fg:#FF0000 bg:color_bg1"
+
+[[battery.display]] # "bold yellow" style when capacity is between 10% and 30%
+threshold = 50
+discharging_symbol = "💦"
+style = "fg:#FFFF00 bg:color_bg1"
+
+[[battery.display]] # "bold green" style when capacity is between 10% and 30%
+threshold = 80
+discharging_symbol = "🔋"
+style = "fg:#7fff00 bg:color_bg1"
+
+
+[time]
+disabled = false
+time_format = "%R"
+style = "bg:color_bg1"
+format = '[[  $time ](fg:color_fg0 bg:color_bg1)]($style)'
+
+
+[line_break]
+disabled = false
+
+[character]
+disabled = false
+success_symbol = '[ ](bold fg:color_green)'
+error_symbol = '[ ](bold fg:color_red)'
+vimcmd_symbol = '[ ](bold fg:color_green)'
+vimcmd_replace_one_symbol = '[  ](bold fg:color_purple)'
+vimcmd_replace_symbol = '[  ](bold fg:color_purple)'
+vimcmd_visual_symbol = '[  ](bold fg:color_yellow)'
diff --git a/ar/.config/stig/rc b/ar/.config/stig/rc
new file mode 100644
index 0000000..90b558f
--- /dev/null
+++ b/ar/.config/stig/rc
@@ -0,0 +1 @@
+set tui.theme themerc
diff --git a/ar/.config/stig/themerc b/ar/.config/stig/themerc
new file mode 100644
index 0000000..eb19aef
--- /dev/null
+++ b/ar/.config/stig/themerc
@@ -0,0 +1,600 @@
+# Color names: default, #1E1E2E, #CDD6F4, #45475A, #BAC2DE, #9D0006,
+# #CC241D, #40A02B, #A6DA95, #2A84D2, #89B4FA, #F38BA8,
+# #F5C2E7, #179299, #94E2D5, #B57614, #F9E2AF
+#
+# Attribute names: bold, underline, standout
+
+cli #1E1E2E on #BAC2DE
+prompt #1E1E2E,bold on #F9E2AF
+find.highlight #1E1E2E on #F9E2AF
+
+tabs.unfocused #CDD6F4 on #1E1E2E
+tabs.focused #1E1E2E on #CDD6F4
+tabs.torrentlist.unfocused #A6DA95 on #45475A
+tabs.torrentlist.focused #1E1E2E on #179299
+tabs.torrentdetails.unfocused #89B4FA on #45475A
+tabs.torrentdetails.focused #1E1E2E on #89B4FA
+tabs.filelist.unfocused #F5C2E7 on #45475A
+tabs.filelist.focused #1E1E2E on #F38BA8
+tabs.peerlist.unfocused #94E2D5 on #45475A
+tabs.peerlist.focused #1E1E2E on #40A02B
+tabs.trackerlist.unfocused #CDD6F4 on #45475A
+tabs.trackerlist.focused #1E1E2E on #BAC2DE
+tabs.settinglist.unfocused #F9E2AF on #2A84D2
+tabs.settinglist.focused #2A84D2 on #F9E2AF
+tabs.help.unfocused #A6DA95 on #2A84D2
+tabs.help.focused #2A84D2 on #A6DA95
+
+$topbar_bg = #2A84D2
+topbar #BAC2DE on $topbar_bg
+topbar.host.connected #CDD6F4 on $topbar_bg
+topbar.host.connecting #1E1E2E on #F5C2E7
+topbar.host.disconnected #1E1E2E on #CC241D
+topbar.help.key #94E2D5 on $topbar_bg
+topbar.help.equals #94E2D5 on $topbar_bg
+topbar.help.label #94E2D5 on $topbar_bg
+topbar.help.space #94E2D5 on $topbar_bg
+
+$bottombar_bg = #45475A
+bottombar #BAC2DE on $bottombar_bg
+bottombar.important #CC241D,bold on $bottombar_bg
+bottombar.marked #1E1E2E,bold on #F9E2AF
+bottombar.bandwidth.up #40A02B on $bottombar_bg
+bottombar.bandwidth.up.highlighted #A6DA95 on $bottombar_bg
+bottombar.bandwidth.down #179299 on $bottombar_bg
+bottombar.bandwidth.down.highlighted #94E2D5 on $bottombar_bg
+
+$log_bg = #45475A
+log #BAC2DE on $log_bg
+log.timestamp #179299 on $log_bg
+log.info #A6DA95 on $log_bg
+log.error #CC241D,bold on $log_bg
+log.debug #F9E2AF on $log_bg
+log.dupecount #94E2D5 on $log_bg
+log.scrollbar #BAC2DE on #45475A
+
+$keychains_bg = #1E1E2E
+keychains #BAC2DE on $keychains_bg
+keychains.header #BAC2DE on #45475A
+keychains.keys #BAC2DE on $keychains_bg
+keychains.keys.next #F9E2AF on $keychains_bg
+keychains.action #CDD6F4 on $keychains_bg
+keychains.description #CDD6F4 on $keychains_bg
+
+$completion_bg = #45475A
+completion #BAC2DE on $completion_bg
+completion.category #BAC2DE,bold,underline on $completion_bg
+completion.item #BAC2DE on $completion_bg
+completion.item.focused #1E1E2E,bold on #F9E2AF
+completion.scrollbar #BAC2DE on $completion_bg
+
+
+helptext #BAC2DE on #1E1E2E
+helptext.scrollbar #BAC2DE on #45475A
+
+
+$tlist_bg.uf = #1E1E2E
+$tlist_bg.f = #45475A
+torrentlist default on $tlist_bg.uf
+torrentlist.focused default on $tlist_bg.f
+torrentlist.header #BAC2DE,underline on $tlist_bg.uf
+torrentlist.scrollbar #BAC2DE on #45475A
+
+$id_fg = #CDD6F4
+torrentlist.id.header $id_fg,underline on $tlist_bg.uf
+torrentlist.id.unfocused $id_fg on $tlist_bg.uf
+torrentlist.id.focused $id_fg on $tlist_bg.f
+
+$downloaded_fg = #179299
+$downloaded_hl = #94E2D5
+torrentlist.downloaded.header $downloaded_fg,underline on $tlist_bg.uf
+torrentlist.downloaded.unfocused $downloaded_fg on $tlist_bg.uf
+torrentlist.downloaded.focused $downloaded_fg on $tlist_bg.f
+torrentlist.downloaded.highlighted.unfocused $downloaded_hl on $tlist_bg.uf
+torrentlist.downloaded.highlighted.focused $downloaded_hl on $tlist_bg.f
+
+$uploaded_fg = #40A02B
+$uploaded_hl = #A6DA95
+torrentlist.uploaded.header $uploaded_fg,underline on $tlist_bg.uf
+torrentlist.uploaded.unfocused $uploaded_fg on $tlist_bg.uf
+torrentlist.uploaded.focused $uploaded_fg on $tlist_bg.f
+torrentlist.uploaded.highlighted.unfocused $uploaded_hl on $tlist_bg.uf
+torrentlist.uploaded.highlighted.focused $uploaded_hl on $tlist_bg.f
+
+$available_fg = #2A84D2
+$available_hl = #89B4FA
+torrentlist.available.header $available_fg,underline on $tlist_bg.uf
+torrentlist.available.unfocused $available_fg on $tlist_bg.uf
+torrentlist.available.focused $available_fg on $tlist_bg.f
+torrentlist.available.highlighted.unfocused $available_hl on $tlist_bg.uf
+torrentlist.available.highlighted.focused $available_hl on $tlist_bg.f
+
+$marked_fg = #CDD6F4
+torrentlist.marked.header $marked_fg,underline on $tlist_bg.uf
+torrentlist.marked.unfocused $marked_fg on $tlist_bg.uf
+torrentlist.marked.focused $marked_fg on $tlist_bg.f
+
+$path_fg = #BAC2DE
+torrentlist.path.header $path_fg,underline on $tlist_bg.uf
+torrentlist.path.unfocused $path_fg on $tlist_bg.uf
+torrentlist.path.focused $path_fg on $tlist_bg.f
+
+$peers_fg = #BAC2DE
+$peers_hl = #CDD6F4
+torrentlist.peers.header $peers_fg,underline on $tlist_bg.uf
+torrentlist.peers.unfocused $peers_fg on $tlist_bg.uf
+torrentlist.peers.focused $peers_fg on $tlist_bg.f
+torrentlist.peers.highlighted.unfocused $peers_hl on $tlist_bg.uf
+torrentlist.peers.highlighted.focused $peers_hl on $tlist_bg.f
+
+$seeds_fg = #BAC2DE
+$seeds_hl = #CDD6F4
+torrentlist.seeds.header $seeds_fg,underline on $tlist_bg.uf
+torrentlist.seeds.unfocused $seeds_fg on $tlist_bg.uf
+torrentlist.seeds.focused $seeds_fg on $tlist_bg.f
+torrentlist.seeds.highlighted.unfocused $seeds_hl on $tlist_bg.uf
+torrentlist.seeds.highlighted.focused $seeds_hl on $tlist_bg.f
+
+$%downloaded_fg = #2A84D2
+$%downloaded_hl = #89B4FA
+torrentlist.%downloaded.header $%downloaded_fg,underline on $tlist_bg.uf
+torrentlist.%downloaded.unfocused $%downloaded_fg on $tlist_bg.uf
+torrentlist.%downloaded.focused $%downloaded_fg on $tlist_bg.f
+torrentlist.%downloaded.highlighted.unfocused $%downloaded_hl on $tlist_bg.uf
+torrentlist.%downloaded.highlighted.focused $%downloaded_hl on $tlist_bg.f
+
+$%available_fg = #2A84D2
+$%available_hl = #89B4FA
+torrentlist.%available.header $%available_fg,underline on $tlist_bg.uf
+torrentlist.%available.unfocused $%available_fg on $tlist_bg.uf
+torrentlist.%available.focused $%available_fg on $tlist_bg.f
+torrentlist.%available.highlighted.unfocused $%available_hl on $tlist_bg.uf
+torrentlist.%available.highlighted.focused $%available_hl on $tlist_bg.f
+
+$rate-down_fg = #179299
+$rate-down_hl = #94E2D5
+torrentlist.rate-down.header $rate-down_fg,underline on $tlist_bg.uf
+torrentlist.rate-down.unfocused $rate-down_fg on $tlist_bg.uf
+torrentlist.rate-down.focused $rate-down_fg on $tlist_bg.f
+torrentlist.rate-down.highlighted.unfocused $rate-down_hl on $tlist_bg.uf
+torrentlist.rate-down.highlighted.focused $rate-down_hl on $tlist_bg.f
+
+$rate-up_fg = #40A02B
+$rate-up_hl = #A6DA95
+torrentlist.rate-up.header $rate-up_fg,underline on $tlist_bg.uf
+torrentlist.rate-up.unfocused $rate-up_fg on $tlist_bg.uf
+torrentlist.rate-up.focused $rate-up_fg on $tlist_bg.f
+torrentlist.rate-up.highlighted.unfocused $rate-up_hl on $tlist_bg.uf
+torrentlist.rate-up.highlighted.focused $rate-up_hl on $tlist_bg.f
+
+$limit-rate-down_fg = #179299
+$limit-rate-down_hl = #94E2D5
+torrentlist.limit-rate-down.header $limit-rate-down_fg,underline on $tlist_bg.uf
+torrentlist.limit-rate-down.unfocused $limit-rate-down_fg on $tlist_bg.uf
+torrentlist.limit-rate-down.focused $limit-rate-down_fg on $tlist_bg.f
+torrentlist.limit-rate-down.highlighted.unfocused $limit-rate-down_hl on $tlist_bg.uf
+torrentlist.limit-rate-down.highlighted.focused $limit-rate-down_hl on $tlist_bg.f
+
+$limit-rate-up_fg = #40A02B
+$limit-rate-up_hl = #A6DA95
+torrentlist.limit-rate-up.header $limit-rate-up_fg,underline on $tlist_bg.uf
+torrentlist.limit-rate-up.unfocused $limit-rate-up_fg on $tlist_bg.uf
+torrentlist.limit-rate-up.focused $limit-rate-up_fg on $tlist_bg.f
+torrentlist.limit-rate-up.highlighted.unfocused $limit-rate-up_hl on $tlist_bg.uf
+torrentlist.limit-rate-up.highlighted.focused $limit-rate-up_hl on $tlist_bg.f
+
+$ratio_fg = #2A84D2
+$ratio_hl = #89B4FA
+torrentlist.ratio.header $ratio_fg,underline on $tlist_bg.uf
+torrentlist.ratio.unfocused $ratio_fg on $tlist_bg.uf
+torrentlist.ratio.focused $ratio_fg on $tlist_bg.f
+torrentlist.ratio.highlighted.unfocused $ratio_hl on $tlist_bg.uf
+torrentlist.ratio.highlighted.focused $ratio_hl on $tlist_bg.f
+
+$size_fg = #F38BA8
+torrentlist.size.header $size_fg,underline on $tlist_bg.uf
+torrentlist.size.unfocused $size_fg on $tlist_bg.uf
+torrentlist.size.focused $size_fg on $tlist_bg.f
+
+$tracker_fg = #BAC2DE
+torrentlist.tracker.header $tracker_fg,underline on $tlist_bg.uf
+torrentlist.tracker.unfocused $tracker_fg on $tlist_bg.uf
+torrentlist.tracker.focused $tracker_fg on $tlist_bg.f
+
+$error_fg = #CC241D
+torrentlist.error.header $error_fg,underline on $tlist_bg.uf
+torrentlist.error.unfocused $error_fg on $tlist_bg.uf
+torrentlist.error.focused $error_fg on $tlist_bg.f
+
+$added_fg = #B57614
+torrentlist.added.header $added_fg,underline on $tlist_bg.uf
+torrentlist.added.unfocused $added_fg on $tlist_bg.uf
+torrentlist.added.focused $added_fg on $tlist_bg.f
+
+$activity_fg = #B57614
+torrentlist.activity.header $activity_fg,underline on $tlist_bg.uf
+torrentlist.activity.unfocused $activity_fg on $tlist_bg.uf
+torrentlist.activity.focused $activity_fg on $tlist_bg.f
+
+$created_fg = #B57614
+torrentlist.created.header $created_fg,underline on $tlist_bg.uf
+torrentlist.created.unfocused $created_fg on $tlist_bg.uf
+torrentlist.created.focused $created_fg on $tlist_bg.f
+
+$completed_fg = #B57614
+$completed_hl = #F9E2AF
+torrentlist.completed.header $completed_fg,underline on $tlist_bg.uf
+torrentlist.completed.unfocused $completed_fg on $tlist_bg.uf
+torrentlist.completed.focused $completed_fg on $tlist_bg.f
+torrentlist.completed.highlighted.unfocused $completed_hl on $tlist_bg.uf
+torrentlist.completed.highlighted.focused $completed_hl on $tlist_bg.f
+
+$eta_fg = #B57614
+$eta_hl = #F9E2AF
+torrentlist.eta.header $eta_fg,underline on $tlist_bg.uf
+torrentlist.eta.unfocused $eta_fg on $tlist_bg.uf
+torrentlist.eta.focused $eta_fg on $tlist_bg.f
+torrentlist.eta.highlighted.unfocused $eta_hl on $tlist_bg.uf
+torrentlist.eta.highlighted.focused $eta_hl on $tlist_bg.f
+
+$started_fg = #B57614
+torrentlist.started.header $started_fg,underline on $tlist_bg.uf
+torrentlist.started.unfocused $started_fg on $tlist_bg.uf
+torrentlist.started.focused $started_fg on $tlist_bg.f
+
+
+$status.idle_fg = #BAC2DE
+$status.downloading_fg = #94E2D5
+$status.uploading_fg = #40A02B
+$status.connected_fg = #F38BA8
+$status.seeding_fg = #BAC2DE
+$status.stopped_fg = #2A84D2
+$status.queued_fg = #B57614
+$status.isolated_fg = #9D0006
+$status.verifying_fg = #F9E2AF
+$status.discovering_fg = #89B4FA
+
+torrentlist.status.header $status.idle_fg,underline on $tlist_bg.uf
+
+torrentlist.status.idle.unfocused $status.idle_fg on $tlist_bg.uf
+torrentlist.status.idle.focused $status.idle_fg on $tlist_bg.f
+
+torrentlist.status.uploading.unfocused $status.uploading_fg on $tlist_bg.uf
+torrentlist.status.uploading.focused $status.uploading_fg on $tlist_bg.f
+
+torrentlist.status.downloading.unfocused $status.downloading_fg on $tlist_bg.uf
+torrentlist.status.downloading.focused $status.downloading_fg on $tlist_bg.f
+
+torrentlist.status.connected.unfocused $status.connected_fg on $tlist_bg.uf
+torrentlist.status.connected.focused $status.connected_fg on $tlist_bg.f
+
+torrentlist.status.seeding.unfocused $status.seeding_fg on $tlist_bg.uf
+torrentlist.status.seeding.focused $status.seeding_fg on $tlist_bg.f
+
+torrentlist.status.stopped.unfocused $status.stopped_fg on $tlist_bg.uf
+torrentlist.status.stopped.focused $status.stopped_fg on $tlist_bg.f
+
+torrentlist.status.isolated.unfocused $status.isolated_fg on $tlist_bg.uf
+torrentlist.status.isolated.focused $status.isolated_fg on $tlist_bg.f
+
+torrentlist.status.queued.unfocused $status.queued_fg on $tlist_bg.uf
+torrentlist.status.queued.focused $status.queued_fg on $tlist_bg.f
+
+torrentlist.status.verifying.unfocused $status.verifying_fg on $tlist_bg.uf
+torrentlist.status.verifying.focused $status.verifying_fg on $tlist_bg.f
+
+torrentlist.status.discovering.unfocused $status.discovering_fg on $tlist_bg.uf
+torrentlist.status.discovering.focused $status.discovering_fg on $tlist_bg.f
+
+
+$name.idle_fg = $status.idle_fg
+torrentlist.name.header $name.idle_fg,underline on $tlist_bg.uf
+torrentlist.name.idle.progress1.unfocused $name.idle_fg,underline on $tlist_bg.uf
+torrentlist.name.idle.progress1.focused $name.idle_fg,underline on $tlist_bg.f
+torrentlist.name.idle.progress2.unfocused $name.idle_fg on $tlist_bg.uf
+torrentlist.name.idle.progress2.focused $name.idle_fg on $tlist_bg.f
+torrentlist.name.idle.complete.unfocused $name.idle_fg on $tlist_bg.uf
+torrentlist.name.idle.complete.focused $name.idle_fg on $tlist_bg.f
+
+$name.seeding_fg = $status.seeding_fg
+torrentlist.name.seeding.progress1.unfocused $name.seeding_fg,underline on $tlist_bg.uf
+torrentlist.name.seeding.progress1.focused $name.seeding_fg,underline on $tlist_bg.f
+torrentlist.name.seeding.progress2.unfocused $name.seeding_fg on $tlist_bg.uf
+torrentlist.name.seeding.progress2.focused $name.seeding_fg on $tlist_bg.f
+torrentlist.name.seeding.complete.unfocused $name.seeding_fg on $tlist_bg.uf
+torrentlist.name.seeding.complete.focused $name.seeding_fg on $tlist_bg.f
+
+$name.uploading_fg = $status.uploading_fg
+torrentlist.name.uploading.progress1.unfocused $name.uploading_fg,underline on $tlist_bg.uf
+torrentlist.name.uploading.progress1.focused $name.uploading_fg,underline on $tlist_bg.f
+torrentlist.name.uploading.progress2.unfocused $name.uploading_fg on $tlist_bg.uf
+torrentlist.name.uploading.progress2.focused $name.uploading_fg on $tlist_bg.f
+torrentlist.name.uploading.complete.unfocused $name.uploading_fg on $tlist_bg.uf
+torrentlist.name.uploading.complete.focused $name.uploading_fg on $tlist_bg.f
+
+$name.downloading_fg = $status.downloading_fg
+torrentlist.name.downloading.progress1.unfocused $name.downloading_fg,underline on $tlist_bg.uf
+torrentlist.name.downloading.progress1.focused $name.downloading_fg,underline on $tlist_bg.f
+torrentlist.name.downloading.progress2.unfocused $name.downloading_fg on $tlist_bg.uf
+torrentlist.name.downloading.progress2.focused $name.downloading_fg on $tlist_bg.f
+torrentlist.name.downloading.complete.unfocused $name.downloading_fg on $tlist_bg.uf
+torrentlist.name.downloading.complete.focused $name.downloading_fg on $tlist_bg.f
+
+$name.isolated_fg = $status.isolated_fg
+torrentlist.name.isolated.progress1.unfocused $name.isolated_fg,underline on $tlist_bg.uf
+torrentlist.name.isolated.progress1.focused $name.isolated_fg,underline on $tlist_bg.f
+torrentlist.name.isolated.progress2.unfocused $name.isolated_fg on $tlist_bg.uf
+torrentlist.name.isolated.progress2.focused $name.isolated_fg on $tlist_bg.f
+torrentlist.name.isolated.complete.unfocused $name.isolated_fg on $tlist_bg.uf
+torrentlist.name.isolated.complete.focused $name.isolated_fg on $tlist_bg.f
+
+$name.connected_fg = $status.connected_fg
+torrentlist.name.connected.progress1.unfocused $name.connected_fg,underline on $tlist_bg.uf
+torrentlist.name.connected.progress1.focused $name.connected_fg,underline on $tlist_bg.f
+torrentlist.name.connected.progress2.unfocused $name.connected_fg on $tlist_bg.uf
+torrentlist.name.connected.progress2.focused $name.connected_fg on $tlist_bg.f
+torrentlist.name.connected.complete.unfocused $name.connected_fg on $tlist_bg.uf
+torrentlist.name.connected.complete.focused $name.connected_fg on $tlist_bg.f
+
+$name.queued_fg = $status.queued_fg
+torrentlist.name.queued.progress1.unfocused $name.queued_fg,underline on $tlist_bg.uf
+torrentlist.name.queued.progress1.focused $name.queued_fg,underline on $tlist_bg.f
+torrentlist.name.queued.progress2.unfocused $name.queued_fg on $tlist_bg.uf
+torrentlist.name.queued.progress2.focused $name.queued_fg on $tlist_bg.f
+torrentlist.name.queued.complete.unfocused $name.queued_fg on $tlist_bg.uf
+torrentlist.name.queued.complete.focused $name.queued_fg on $tlist_bg.f
+
+$name.stopped_fg = $status.stopped_fg
+torrentlist.name.stopped.progress1.unfocused $name.stopped_fg,underline on $tlist_bg.uf
+torrentlist.name.stopped.progress1.focused $name.stopped_fg,underline on $tlist_bg.f
+torrentlist.name.stopped.progress2.unfocused $name.stopped_fg on $tlist_bg.uf
+torrentlist.name.stopped.progress2.focused $name.stopped_fg on $tlist_bg.f
+torrentlist.name.stopped.complete.unfocused $name.stopped_fg on $tlist_bg.uf
+torrentlist.name.stopped.complete.focused $name.stopped_fg on $tlist_bg.f
+
+$name.verifying_fg = $status.verifying_fg
+torrentlist.name.verifying.progress1.unfocused $name.verifying_fg,underline on $tlist_bg.uf
+torrentlist.name.verifying.progress1.focused $name.verifying_fg,underline on $tlist_bg.f
+torrentlist.name.verifying.progress2.unfocused $name.verifying_fg on $tlist_bg.uf
+torrentlist.name.verifying.progress2.focused $name.verifying_fg on $tlist_bg.f
+torrentlist.name.verifying.complete.unfocused $name.verifying_fg on $tlist_bg.uf
+torrentlist.name.verifying.complete.focused $name.verifying_fg on $tlist_bg.f
+
+$name.discovering_fg = $status.discovering_fg
+torrentlist.name.discovering.progress1.unfocused $name.discovering_fg,underline on $tlist_bg.uf
+torrentlist.name.discovering.progress1.focused $name.discovering_fg,underline on $tlist_bg.f
+torrentlist.name.discovering.progress2.unfocused $name.discovering_fg on $tlist_bg.uf
+torrentlist.name.discovering.progress2.focused $name.discovering_fg on $tlist_bg.f
+torrentlist.name.discovering.complete.unfocused $name.discovering_fg on $tlist_bg.uf
+torrentlist.name.discovering.complete.focused $name.discovering_fg on $tlist_bg.f
+
+
+torrentdetails #BAC2DE on #1E1E2E
+torrentdetails.error #CC241D on #1E1E2E
+torrentdetails.scrollbar #BAC2DE on #45475A
+
+
+$flist_bg.uf = #1E1E2E
+$flist_bg.f = #45475A
+filelist default on $flist_bg.uf
+filelist.focused default on $flist_bg.f
+filelist.header #BAC2DE,underline on $flist_bg.uf
+filelist.scrollbar #BAC2DE on #45475A
+
+$marked_fg = #CDD6F4
+filelist.marked.header $marked_fg,underline on $flist_bg.uf
+filelist.marked.unfocused $marked_fg on $flist_bg.uf
+filelist.marked.focused $marked_fg on $flist_bg.f
+
+$file_fg = #BAC2DE
+$folder_fg = #CDD6F4
+filelist.name.header #BAC2DE,underline on $flist_bg.uf
+filelist.name.file.unfocused $file_fg on $flist_bg.uf
+filelist.name.file.focused $file_fg on $flist_bg.f
+filelist.name.folder.unfocused $folder_fg on $flist_bg.uf
+filelist.name.folder.focused $folder_fg on $flist_bg.f
+
+$size_fg = #F38BA8
+filelist.size.header $size_fg,underline on $flist_bg.uf
+filelist.size.unfocused $size_fg on $flist_bg.uf
+filelist.size.focused $size_fg on $flist_bg.f
+
+$downloaded_fg = #179299
+$downloaded_hl = #94E2D5
+filelist.downloaded.header $downloaded_fg,underline on $flist_bg.uf
+filelist.downloaded.unfocused $downloaded_fg on $flist_bg.uf
+filelist.downloaded.focused $downloaded_fg on $flist_bg.f
+filelist.downloaded.highlighted.unfocused $downloaded_hl on $flist_bg.uf
+filelist.downloaded.highlighted.focused $downloaded_hl on $flist_bg.f
+
+$%downloaded_fg = #2A84D2
+$%downloaded_hl = #89B4FA
+filelist.%downloaded.header $%downloaded_fg,underline on $flist_bg.uf
+filelist.%downloaded.unfocused $%downloaded_fg on $flist_bg.uf
+filelist.%downloaded.focused $%downloaded_fg on $flist_bg.f
+filelist.%downloaded.highlighted.unfocused $%downloaded_hl on $flist_bg.uf
+filelist.%downloaded.highlighted.focused $%downloaded_hl on $flist_bg.f
+
+$priority_fg = #B57614
+$priority_hl = #F9E2AF
+filelist.priority.header $priority_fg,underline on $flist_bg.uf
+filelist.priority.unfocused $priority_fg on $flist_bg.uf
+filelist.priority.focused $priority_fg on $flist_bg.f
+filelist.priority.low.unfocused $priority_fg on $flist_bg.uf
+filelist.priority.low.focused $priority_fg on $flist_bg.f
+filelist.priority.high.unfocused $priority_hl on $flist_bg.uf
+filelist.priority.high.focused $priority_hl on $flist_bg.f
+filelist.priority.off.unfocused #2A84D2 on $flist_bg.uf
+filelist.priority.off.focused #2A84D2 on $flist_bg.f
+
+
+$plist_bg = #1E1E2E
+peerlist default on $plist_bg
+peerlist.focused default on $plist_bg
+peerlist.header #BAC2DE,underline on $plist_bg
+peerlist.scrollbar #BAC2DE on #45475A
+
+$torrent_fg = #BAC2DE
+peerlist.torrent.header $torrent_fg,underline on $plist_bg
+peerlist.torrent.unfocused $torrent_fg on $plist_bg
+
+$host_fg = #BAC2DE
+peerlist.host.header $host_fg,underline on $plist_bg
+peerlist.host.unfocused $host_fg on $plist_bg
+
+$port_fg = #BAC2DE
+peerlist.port.header $port_fg,underline on $plist_bg
+peerlist.port.unfocused $port_fg on $plist_bg
+
+$client_fg = #F38BA8
+peerlist.client.header $client_fg,underline on $plist_bg
+peerlist.client.unfocused $client_fg on $plist_bg
+
+$%downloaded_fg = #2A84D2
+$%downloaded_hl = #89B4FA
+peerlist.%downloaded.header $%downloaded_fg,underline on $plist_bg
+peerlist.%downloaded.unfocused $%downloaded_fg on $plist_bg
+peerlist.%downloaded.highlighted.unfocused $%downloaded_hl on $plist_bg
+
+$rate-down_fg = #179299
+$rate-down_hl = #94E2D5
+peerlist.rate-down.header $rate-down_fg,underline on $plist_bg
+peerlist.rate-down.unfocused $rate-down_fg on $plist_bg
+peerlist.rate-down.highlighted.unfocused $rate-down_hl on $plist_bg
+
+$rate-up_fg = #40A02B
+$rate-up_hl = #A6DA95
+peerlist.rate-up.header $rate-up_fg,underline on $plist_bg
+peerlist.rate-up.unfocused $rate-up_fg on $plist_bg
+peerlist.rate-up.highlighted.unfocused $rate-up_hl on $plist_bg
+
+$rate-est_fg = #40A02B
+$rate-est_hl = #A6DA95
+peerlist.rate-est.header $rate-est_fg,underline on $plist_bg
+peerlist.rate-est.unfocused $rate-est_fg on $plist_bg
+peerlist.rate-est.highlighted.unfocused $rate-est_hl on $plist_bg
+
+$eta_fg = #B57614
+$eta_hl = #F9E2AF
+peerlist.eta.header $eta_fg,underline on $plist_bg
+peerlist.eta.unfocused $eta_fg on $plist_bg
+peerlist.eta.highlighted.unfocused $eta_hl on $plist_bg
+
+
+$trklist_bg.uf = #1E1E2E
+$trklist_bg.f = #45475A
+trackerlist default on $trklist_bg.uf
+trackerlist.focused default on $trklist_bg.f
+trackerlist.header #BAC2DE,underline on $trklist_bg.uf
+trackerlist.scrollbar #BAC2DE on #45475A
+
+$torrent_fg = #BAC2DE
+trackerlist.torrent.header $torrent_fg,underline on $trklist_bg.uf
+trackerlist.torrent.unfocused $tracker_fg on $trklist_bg.uf
+trackerlist.torrent.focused $tracker_fg on $trklist_bg.f
+
+$tier_fg = #B57614
+trackerlist.tier.header $tier_fg,underline on $trklist_bg.uf
+trackerlist.tier.unfocused $tier_fg on $trklist_bg.uf
+trackerlist.tier.focused $tier_fg on $trklist_bg.f
+
+$domain_fg = #F38BA8
+trackerlist.domain.header $domain_fg,underline on $trklist_bg.uf
+trackerlist.domain.unfocused $domain_fg on $trklist_bg.uf
+trackerlist.domain.focused $domain_fg on $trklist_bg.f
+
+$url-announce_fg = #89B4FA
+trackerlist.url-announce.header $url-announce_fg,underline on $trklist_bg.uf
+trackerlist.url-announce.unfocused $url-announce_fg on $trklist_bg.uf
+trackerlist.url-announce.focused $url-announce_fg on $trklist_bg.f
+
+$url-scrape_fg = #89B4FA
+trackerlist.url-scrape.header $url-scrape_fg,underline on $trklist_bg.uf
+trackerlist.url-scrape.unfocused $url-scrape_fg on $trklist_bg.uf
+trackerlist.url-scrape.focused $url-scrape_fg on $trklist_bg.f
+
+$status_fg = #179299
+trackerlist.status.header $status_fg,underline on $trklist_bg.uf
+trackerlist.status.unfocused $status_fg on $trklist_bg.uf
+trackerlist.status.focused $status_fg on $trklist_bg.f
+
+$error_fg = #9D0006
+trackerlist.error.header $error_fg,underline on $trklist_bg.uf
+trackerlist.error.unfocused $error_fg on $trklist_bg.uf
+trackerlist.error.focused $error_fg on $trklist_bg.f
+
+$error-announce_fg = #9D0006
+trackerlist.error-announce.header $error_fg,underline on $trklist_bg.uf
+trackerlist.error-announce.unfocused $error_fg on $trklist_bg.uf
+trackerlist.error-announce.focused $error_fg on $trklist_bg.f
+
+$error-scrape_fg = #9D0006
+trackerlist.error-scrape.header $error_fg,underline on $trklist_bg.uf
+trackerlist.error-scrape.unfocused $error_fg on $trklist_bg.uf
+trackerlist.error-scrape.focused $error_fg on $trklist_bg.f
+
+$downloads_fg = #BAC2DE
+trackerlist.downloads.header $downloads_fg,underline on $trklist_bg.uf
+trackerlist.downloads.unfocused $downloads_fg on $trklist_bg.uf
+trackerlist.downloads.focused $downloads_fg on $trklist_bg.f
+
+$leeches_fg = #BAC2DE
+trackerlist.leeches.header $leeches_fg,underline on $trklist_bg.uf
+trackerlist.leeches.unfocused $leeches_fg on $trklist_bg.uf
+trackerlist.leeches.focused $leeches_fg on $trklist_bg.f
+
+$seeds_fg = #BAC2DE
+trackerlist.seeds.header $seeds_fg,underline on $trklist_bg.uf
+trackerlist.seeds.unfocused $seeds_fg on $trklist_bg.uf
+trackerlist.seeds.focused $seeds_fg on $trklist_bg.f
+
+$last-announce_fg = #CDD6F4
+trackerlist.last-announce.header $last-announce_fg,underline on $trklist_bg.uf
+trackerlist.last-announce.unfocused $last-announce_fg on $trklist_bg.uf
+trackerlist.last-announce.focused $last-announce_fg on $trklist_bg.f
+
+$next-announce_fg = #CDD6F4
+trackerlist.next-announce.header $next-announce_fg,underline on $trklist_bg.uf
+trackerlist.next-announce.unfocused $next-announce_fg on $trklist_bg.uf
+trackerlist.next-announce.focused $next-announce_fg on $trklist_bg.f
+
+$last-scrape_fg = #CDD6F4
+trackerlist.last-scrape.header $last-scrape_fg,underline on $trklist_bg.uf
+trackerlist.last-scrape.unfocused $last-scrape_fg on $trklist_bg.uf
+trackerlist.last-scrape.focused $last-scrape_fg on $trklist_bg.f
+
+$next-scrape_fg = #CDD6F4
+trackerlist.next-scrape.header $next-scrape_fg,underline on $trklist_bg.uf
+trackerlist.next-scrape.unfocused $next-scrape_fg on $trklist_bg.uf
+trackerlist.next-scrape.focused $next-scrape_fg on $trklist_bg.f
+
+
+$slist_bg.uf = #1E1E2E
+$slist_bg.f = #45475A
+settinglist default on $slist_bg.uf
+settinglist.focused default on $slist_bg.f
+settinglist.header #BAC2DE,underline on $slist_bg.uf
+settinglist.scrollbar #BAC2DE on #45475A
+
+$name_fg = #89B4FA
+settinglist.name.header $name_fg,underline on $slist_bg.uf
+settinglist.name.unfocused $name_fg on $slist_bg.uf
+settinglist.name.focused $name_fg on $slist_bg.f
+
+$value_fg = #BAC2DE
+$value_hl = #CDD6F4,bold
+settinglist.value.header $value_fg,underline on $slist_bg.uf
+settinglist.value.unfocused $value_fg on $slist_bg.uf
+settinglist.value.focused $value_fg on $slist_bg.f
+settinglist.value.highlighted.unfocused $value_hl on $slist_bg.uf
+settinglist.value.highlighted.focused $value_hl on $slist_bg.f
+
+$default_fg = #BAC2DE
+settinglist.default.header $default_fg,underline on $slist_bg.uf
+settinglist.default.unfocused $default_fg on $slist_bg.uf
+settinglist.default.focused $default_fg on $slist_bg.f
+
+$description_fg = #BAC2DE
+settinglist.description.header $description_fg,underline on $slist_bg.uf
+settinglist.description.unfocused $description_fg on $slist_bg.uf
+settinglist.description.focused $description_fg on $slist_bg.f
diff --git a/ar/.config/task/taskrc b/ar/.config/task/taskrc
new file mode 100644
index 0000000..665a077
--- /dev/null
+++ b/ar/.config/task/taskrc
@@ -0,0 +1,191 @@
+# [Created by task 3.1.0 10/5/2024 17:27:41]
+news.version=3.2.0
+
+# Taskwarrior program configuration file.
+# For more documentation, see https://taskwarrior.org or try 'man task', 'man task-color',
+# 'man task-sync' or 'man taskrc'
+
+# Here is an example of entries that use the default, override and blank values
+# variable=foo -- By specifying a value, this overrides the default
+# variable= -- By specifying no value, this means no default
+# #variable=foo -- By commenting out the line, or deleting it, this uses the default
+
+# You can also refence environment variables:
+# variable=$HOME/task
+# variable=$VALUE
+
+# Use the command 'task show' to see all defaults and overrides
+# Defaults
+default.project=si
+
+# To use the default location of the XDG directories,
+# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and update location config as follows:
+
+data.location=~/.local/share/task
+hooks.location=~/.local/bin/task/hooks
+
+# Color theme (uncomment one to use)
+#include light-16.theme
+#include light-256.theme
+#include dark-16.theme
+#include dark-256.theme
+#include dark-red-256.theme
+#include dark-green-256.theme
+#include dark-blue-256.theme
+#include dark-violets-256.theme
+include dark-yellow-green.theme
+#include dark-gray-256.theme
+#include dark-gray-blue-256.theme
+#include solarized-dark-256.theme
+#include solarized-light-256.theme
+#include no-color.theme
+
+# Options
+search.case.sensitive=no
+
+# Sync
+sync.encryption_secret=VO,mr[Pg&[Y^m]hu=zL
+sync.gcp.bucket=task.thesiah.xyz
+sync.gcp.credential_path=~/.local/share/task/task-sync-server.json
+
+# Custom color
+color.blocked = yellow on black
+color.blocking = yellow on black
+color.tag.epic = bold green
+color.tag.review = bold blue
+color.tag.bug = magenta on black
+color.project.Supertype=black on blue
+color.project.Algoritma=black on blue
+color.uda.effort.s=color180
+color.uda.effort.m=color210
+color.uda.effort.l=color255
+color.uda.effort.xl=color255
+color.uda.effort.xxl=color255
+
+# Custom contexts
+context.home.read=+home
+context.home.write=+home
+context.integration.read=+integration
+context.integration.write=+integration
+context.temp.read=+temp
+context.temp.write=+temp
+context.work.read=+work
+context.work.write=+work
+
+# Custom reports
+report.all-projects.columns=project
+report.all-projects.description=List of current projects in home context
+report.all-projects.labels=
+report.all-projects.sort=project+
+
+report.currentall.columns=id,entry.age,manual_priority,project,tags,description.desc,entry,due.relative,follow
+report.currentall.description=List of current tasks in the home context
+report.currentall.filter=status:pending
+report.currentall.labels=ID,Age,P,Proj,Tag,Desc,Created,Due,Follow
+report.currentall.sort=manual_priority-,project+
+
+report.current.columns=id,entry.age,manual_priority,priority,project,tags,description.desc,entry,due.relative,follow,depends,urgency
+report.current.description=List of current tasks in the home context
+report.current.filter=status:pending -BLOCKING
+report.current.labels=ID,Age,MP,P,Proj,Tag,Desc,Created,Due,Follow,Depends,Urg
+report.current.sort=manual_priority-,priority-,project+
+
+report.current-projects.columns=project
+report.current-projects.description=List of current projects in home context
+report.current-projects.filter=status:pending
+report.current-projects.labels=
+report.current-projects.sort=project+
+
+report.next.columns=id,start.age,entry.age,depends,priority,effort,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description,urgency,score
+report.next.labels=ID,Active,Age,Deps,P,💪,Project,Tag,Recur,Sche,Due,Until,Description,Urg,🏆
+
+report.tmark.columns=project,description.desc
+report.tmark.description=List of tasks to report
+report.tmark.filter=status:pending -idea
+report.tmark.labels=Proj,Desc
+report.tmark.sort=project+
+
+report.tmark-done.columns=project,description.desc
+report.tmark-done.description=List of tasks to report done
+report.tmark-done.filter=status:completed -idea
+report.tmark-done.labels=Proj,Desc
+report.tmark-done.sort=project+
+
+report.tmark-next.columns=project,description.desc
+report.tmark-next.description=List of tasks to report next
+report.tmark-next.filter=status:pending -idea +next
+report.tmark-next.labels=Proj,Desc
+report.tmark-next.sort=project+\/
+
+report.tmark-yesterday.columns=project,description.desc
+report.tmark-yesterday.description=List of tasks completed yesterday
+report.tmark-yesterday.filter=status:completed end:yesterday
+report.tmark-yesterday.labels=Proj,Desc
+report.tmark-yesterday.sort=project+\/
+
+report.workdone.columns=project,description.desc,end
+report.workdone.description=List of completed tasks in the work context
+report.workdone.filter=status:completed description!="fill standup forms"
+report.workdone.labels=Proj,Desc,Done
+report.workdone.sort=end-\/,project+
+
+report._reviewed.columns=uuid
+report._reviewed.description=Tasksh review report. Adjust the filter to your needs.
+report._reviewed.filter=( reviewed.none: or reviewed.before:now-1week ) and ( +PENDING or +WAITING )
+report._reviewed.sort=reviewed+,modified+
+
+# Custom attributes
+uda.effort.type=string
+uda.effort.label=Effort
+uda.effort.values=s,m,l,xl,xxl
+uda.follow.type=string
+uda.follow.label=Follow
+uda.follow.values=Y,N
+uda.linear_issue_id.type=string
+uda.linear_issue_id.label=Linear Issue ID
+uda.manual_priority.type=numeric
+uda.manual_priority.label=Manual Priority
+uda.release.type=string
+uda.release.label=Release
+uda.reviewed.type=date
+uda.reviewed.label=Reviewed
+uda.score.type=numeric
+uda.score.label=Score 🏆
+uda.session.type=string
+uda.session.label=Session
+urgency.uda.effort.s.coefficient=1.5
+urgency.uda.effort.m.coefficient=1.1
+urgency.uda.effort.l.coefficient=1
+urgency.uda.effort.xl.coefficient=0
+urgency.uda.effort.xxl.coefficient=-0.2
+urgency.uda.score.coefficient=2
+
+# Taskwarrior tui overrides
+uda.taskwarrior-tui.task-report.jump-on-task-add=true
+context.archive.read=+archive
+context.archive.write=+archive
+context.integration.read=+integration
+context.integration.write=+integration
+
+# Takswarrior tui scripts
+uda.taskwarrior-tui.task-report.next.filter=status:pending
+uda.taskwarrior-tui.shortcuts.1=~/.local/bin/task/taskwarrior-tui/taskopen-annotation
+uda.taskwarrior-tui.shortcuts.2=~/.local/bin/task/taskwarrior-tui/cycle-tmux-projects
+uda.taskwarrior-tui.shortcuts.3=~/.local/bin/task/taskwarrior-tui/github-issue-sync
+uda.taskwarrior-tui.shortcuts.4=~/.local/bin/task/taskwarrior-tui/cycle-priority
+uda.taskwarrior-tui.shortcuts.5=~/.local/bin/task/taskwarrior-tui/decrease-task-priority
+uda.taskwarrior-tui.shortcuts.6=~/.local/bin/task/taskwarrior-tui/increase-task-priority
+uda.taskwarrior-tui.shortcuts.7=~/.local/bin/task/taskwarrior-tui/annotate-with-note
+uda.taskwarrior-tui.shortcuts.8=~/.local/bin/task/taskwarrior-tui/annotate-with-new-note
+uda.taskwarrior-tui.shortcuts.9=~/.local/bin/task/taskwarrior-tui/toggle-review-label
+uda.taskwarrior-tui.shortcuts.0=~/.local/bin/task/taskwarrior-tui/task-switch-context
+
+#Taskwarrior tui selection
+uda.taskwarrior-tui.selection.indicator=->
+uda.taskwarrior-tui.selection.bold=yes
+uda.taskwarrior-tui.selection.italic=no
+uda.taskwarrior-tui.selection.dim=yes
+uda.taskwarrior-tui.selection.blink=no
+uda.taskwarrior-tui.selection.reverse=yes
+uda.taskwarrior-tui.task-report.show-info=false
+context=home
diff --git a/ar/.config/taskopen/taskopenrc b/ar/.config/taskopen/taskopenrc
new file mode 100644
index 0000000..36c99d7
--- /dev/null
+++ b/ar/.config/taskopen/taskopenrc
@@ -0,0 +1,30 @@
+[General]
+taskbin=task
+taskargs
+no_annotation_hook="addnote $ID"
+task_attributes="priority,project,tags,description"
+--sort:"urgency-,annot"
+--active-tasks:"+PENDING"
+EDITOR=nvim
+path_ext=/usr/share/taskopen/scripts
+[Actions]
+nvimline.target=annotations
+nvimline.labelregex=".*"
+nvimline.regex="^nvimline:(\\d+):(.*)"
+nvimline.command="/home/si/.local/bin/taksopenline $FILE $TASK_DESCRIPTION"
+nvimline.modes="batch,any,normal"
+files.target=annotations
+files.labelregex=".*"
+files.regex="^[\\.\\/~]+.*\\.(.*)"
+files.command="$EDITOR $FILE"
+files.modes="batch,any,normal"
+notes.target=annotations
+notes.labelregex=".*"
+notes.regex="^Notes(\\..*)?"
+notes.command="""editnote ~/Notes/tasknotes/$UUID$LAST_MATCH "$TASK_DESCRIPTION" $UUID"""
+notes.modes="batch,any,normal"
+url.target=annotations
+url.labelregex=".*"
+url.regex="((?:www|http).*)"
+url.command="open $LAST_MATCH"
+url.modes="batch,any,normal"
diff --git a/ar/.config/tmux/tmux.conf b/ar/.config/tmux/tmux.conf
new file mode 100644
index 0000000..21bd6c1
--- /dev/null
+++ b/ar/.config/tmux/tmux.conf
@@ -0,0 +1,303 @@
+### --- TPM --- ###
+set-environment -gF TMUX_PLUGIN_MANAGER_PATH '#{HOME}/.local/bin/tmux/'
+
+if 'test ! -d "${TMUX_PLUGIN_MANAGER_PATH}/tpm"' {
+ run 'mkdir -p "${TMUX_PLUGIN_MANAGER_PATH}" &&
+ git clone https://github.com/tmux-plugins/tpm "${TMUX_PLUGIN_MANAGER_PATH}/tpm" &&
+ ${TMUX_PLUGIN_MANAGER_PATH}/tpm/bin/install_plugins'
+}
+
+
+### --- Settings --- ###
+# general
+set -g base-index 1
+set -g pane-base-index 1
+set -g default-shell /usr/bin/zsh
+set -g detach-on-destroy off
+set -g focus-events on
+set -g history-limit 1000000
+set -g mouse on
+set -g renumber-windows on
+set -g set-clipboard on
+set -g xterm-keys on
+set -gq allow-passthrough on
+set -sg escape-time 0
+setw -g mode-keys vi
+setw -g aggressive-resize on
+
+# activity
+set -g monitor-activity on
+set -g visual-activity off
+
+# color
+set -g default-terminal "tmux-256color"
+# set -g default-terminal 'screen-256color'
+set -ga terminal-overrides ',xterm-256color:Tc'
+set -sa terminal-features ',xterm-256color:RGB'
+
+# status
+set -g status-interval 1
+set -g status-justify absolute-centre
+set -g status-left ""
+set -g status-left-length 10
+set -g status-position top
+set -g status-right ""
+set -g status-style "bg=default"
+
+
+### --- Key Bindings --- ###
+# clear
+bind 'l' "send-keys C-l; send-keys -R; clear-history"
+
+# lazygit
+bind -n 'M-g' popup -d "#{pane_current_path}" -E -h 95% -w 95% -x 100% "EDITOR=nvim lazygit"
+
+# pane
+bind '%' split-window -h -c '#{pane_current_path}'
+bind '"' split-window -v -c '#{pane_current_path}'
+bind '|' split-window -h
+bind '-' split-window -v
+bind -r 'C-down' resize-pane -D 5
+bind -r 'C-up' resize-pane -U 5
+bind -r 'C-left' resize-pane -L 5
+bind -r 'C-right' resize-pane -R 5
+bind '@' choose-window 'join-pane -h -s "%%"'
+bind '#' choose-window 'join-pane -s "%%"'
+bind 'x' kill-pane
+bind -n 'M-k' move-pane -h -t '.{up-of}'
+bind -n 'M-l' move-pane -t '.{right-of}'
+bind -n 'M-h' move-pane -t '.{left-of}'
+bind -n 'M-j' move-pane -h -t '.{down-of}'
+
+# prefix
+unbind 'C-b'
+set -g prefix 'C-Space'
+bind 'C-Space' send-prefix
+
+# reload
+unbind 'r'
+bind 'r' source-file "~/.config/tmux/tmux.conf"
+
+# scripts
+bind 'h' run-shell -b "~/.local/bin/tmuxtogglebar"
+bind 'b' run-shell -b "~/.local/bin/tmuxtoggleborder"
+bind -T copy-mode M-\\ run-shell -b "~/.local/bin/tmuxtoggleterm"
+bind -T copy-mode-vi M-\\ run-shell -b "~/.local/bin/tmuxtoggleterm"
+bind -n 'C-t' run-shell "~/.local/bin/sessionizer"
+bind -n 'M-t' run-shell "~/.local/bin/sessionizer"
+bind -T copy-mode M-t run-shell "~/.local/bin/sessionizer"
+bind -T copy-mode-vi M-t run-shell "~/.local/bin/sessionizer"
+
+# sesh
+bind -N "last-session (via sesh)" -n "M-'" run-shell 'sesh last'
+bind -N "switch to root session (via sesh)" 9 run-shell 'sesh connect --root "$(pwd)"'
+
+# session
+bind -r '(' switch-client -p \; refresh-client -S
+bind -nr 'M-P' switch-client -p \; refresh-client -S
+bind -r ')' switch-client -n \; refresh-client -S
+bind -nr 'M-N' switch-client -n \; refresh-client -S
+bind "'" switch-client -t'{marked}'
+bind -r 'BSpace' switch-client -l
+
+# window
+bind -r '<' swap-window -d -t -1
+bind -r '>' swap-window -d -t +1
+bind -nr 'M-H' previous-window \; refresh-client -S
+bind -nr 'M-L' next-window \; refresh-client -S
+bind 'c' new-window -c "#{pane_current_path}"
+bind -nr 'M-BSpace' last-window
+
+
+### --- Plugins --- ###
+set -g @plugin 'tmux-plugins/tpm'
+set -g @plugin 'alexwforsythe/tmux-which-key'
+set -g @plugin 'aserowy/tmux.nvim'
+set -g @plugin 'catppuccin/tmux#v2.1.2'
+set -g @plugin 'olimorris/tmux-pomodoro-plus'
+set -g @plugin 'sainnhe/tmux-fzf'
+set -g @plugin 'tmux-plugins/tmux-battery'
+set -g @plugin 'tmux-plugins/tmux-continuum'
+set -g @plugin 'tmux-plugins/tmux-copycat'
+set -g @plugin 'tmux-plugins/tmux-cpu'
+set -g @plugin 'tmux-plugins/tmux-online-status'
+set -g @plugin 'tmux-plugins/tmux-open'
+set -g @plugin 'tmux-plugins/tmux-resurrect'
+set -g @plugin 'tmux-plugins/tmux-sensible'
+set -g @plugin 'tmux-plugins/tmux-yank'
+set -g @plugin 'xamut/tmux-weather'
+
+
+### --- Plugins Settings --- ###
+# continuum
+# set -g @continuum-boot 'on'
+# set -g @continuum-boot-options 'kitty'
+# set -g @continuum-restore 'on'
+# set -g @continuum-save-interval '1' # min, 0 for disable
+
+# copycat
+bind -T copy-mode-vi 'v' send-keys -X begin-selection
+bind -T copy-mode-vi 'C-v' send-keys -X rectangle-toggle
+bind -T copy-mode-vi 'y' send-keys -X copy-selection-and-cancel
+
+# navigation
+set -g @tmux-nvim-navigation true
+set -g @tmux-nvim-navigation-cycle false
+set -g @tmux-nvim-navigation-keybinding-left 'C-h'
+set -g @tmux-nvim-navigation-keybinding-down 'C-j'
+set -g @tmux-nvim-navigation-keybinding-up 'C-k'
+set -g @tmux-nvim-navigation-keybinding-right 'C-l'
+set -g @tmux-nvim-resize true
+set -g @tmux-nvim-resize-step-x 2
+set -g @tmux-nvim-resize-step-y 2
+set -g @tmux-nvim-resize-keybinding-left 'C-left'
+set -g @tmux-nvim-resize-keybinding-down 'C-down'
+set -g @tmux-nvim-resize-keybinding-up 'C-up'
+set -g @tmux-nvim-resize-keybinding-right 'C-right'
+
+# open
+set -g @open 'O'
+set -g @open-editor 'e'
+set -g @open-W 'https://www.searx.thesiah.xyz/?q='
+
+# pomodoro
+set -g @pomodoro_toggle 'a' # Start/pause a Pomodoro/break
+set -g @pomodoro_cancel 'A' # Cancel the current session
+set -g @pomodoro_menu 'C-a' # Pomodoro menu
+set -g @pomodoro_restart 'R' # Restart
+set -g @pomodoro_skip 'B' # Skip a Pomodoro/break
+set -g @pomodoro_custom '' # Custom menu
+set -g @pomodoro_mins 25 # The duration of the Pomodoro
+set -g @pomodoro_break_mins 5 # The duration of the break after the Pomodoro completes
+set -g @pomodoro_intervals 4 # The number of intervals before a longer break is started
+set -g @pomodoro_interval_display "[%s/%s]"
+set -g @pomodoro_long_break_mins 25 # The duration of the long break
+set -g @pomodoro_repeat 'off' # Automatically repeat the Pomodoros?
+set -g @pomodoro_disable_breaks 'off' # Turn off breaks
+set -g @pomodoro_on "#[fg=#{@thm_red}] "
+set -g @pomodoro_complete "#[fg=#{@thm_red}]󱫑 "
+set -g @pomodoro_pause "#[fg=#{@thm_yellow}]󱫟 "
+set -g @pomodoro_prompt_break "#[fg=#{@thm_mauve}]󱫫 "
+set -g @pomodoro_prompt_pomodoro "#[fg=#{@thm_green}]󱫡 "
+set -g @pomodoro_sound 'on' # Sound for desktop notifications (Run `ls /System/Library/Sounds` for a list of sounds to use on Mac)
+set -g @pomodoro_notifications 'off'
+set -g @pomodoro_granularity 'on'
+
+# resurrect
+set -g @resurrect-save 'S'
+set -g @resurrect-restore 'C-r'
+
+# tmux-fzf
+TMUX_FZF_LAUNCH_KEY="F"
+TMUX_FZF_ORDER='session|window|pane|command|keybinding|clipboard|process'
+TMUX_FZF_OPTIONS="-p -w 75% -h 85% -m"
+TMUX_FZF_PANE_FORMAT=' \
+ [#{window_name}] \
+ #{pane_current_command} \
+ [#{pane_width}x#{pane_height}] \
+ [history #{history_size}/#{history_limit}, #{history_bytes} bytes] \
+ #{?pane_active,[active],[inactive]}'
+
+bind 'C-b' run-shell -b "~/.local/bin/tmux/tmux-fzf/scripts/keybinding.sh"
+bind 'C-c' run-shell -b "~/.local/bin/tmux/tmux-fzf/scripts/command.sh"
+bind 'C-k' run-shell -b "~/.local/bin/tmux/tmux-fzf/scripts/process.sh"
+bind 'C-p' run-shell -b "~/.local/bin/tmux/tmux-fzf/scripts/pane.sh"
+bind 'C-s' run-shell -b "~/.local/bin/tmux/tmux-fzf/scripts/session.sh"
+bind 'C-w' run-shell -b "~/.local/bin/tmux/tmux-fzf/scripts/window.sh"
+
+# tpm
+set -g @tpm-clean 'X'
+
+
+### --- Theme --- ###
+# catppuccin
+set -g @catppuccin_flavor "mocha"
+set -g @catppuccin_pane_border_status "off"
+set -g @catppuccin_pane_status_enabled "off"
+set -g @catppuccin_window_number_position "left"
+set -g @catppuccin_window_status_style "none" # basic or none
+set -g @catppuccin_status_background "none"
+set -g @catppuccin_window_left_separator ""
+set -g @catppuccin_window_middle_separator ""
+set -g @catppuccin_window_right_separator ""
+set -g @catppuccin_window_current_middle_separator ""
+set -ogq @catppuccin_status_left_separator ""
+set -ogq @catppuccin_status_middle_separator ""
+set -ogq @catppuccin_status_right_separator ""
+set -ogq @catppuccin_status_connect_separator "yes" # yes, no
+set -ogq @catppuccin_status_module_bg_color "none"
+
+# online
+set -g @online_icon "ok"
+set -g @offline_icon "nok"
+
+# status left style
+set -ogq @thm_bg "default" # transparent background
+set -g status-left ""
+set -g status-left-length 90
+set -ga status-left "#[bg=default,fg=#{@thm_red}] #(tmux list-sessions | nl | grep -w \"$(tmux display-message -p '#S')\" | awk '{print \$1}')/#(tmux list-sessions | wc -l) "
+set -ga status-left "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-left "#{?client_prefix,#{#[bg=#{@thm_surface_2},fg=#{@thm_green},bold,italics]  #S },#{#[bg=default,fg=#{@thm_green}]  #S }}"
+set -ga status-left "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-left "#[bg=default,fg=#{@thm_subtext_0}]  #{pane_current_command} "
+set -ga status-left "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-left "#[bg=default,fg=#{@thm_blue}]  #{=/-32/...:#{s|$USER|~|:#{b:pane_current_path}}} "
+set -ga status-left "#[bg=default,fg=#{@thm_overlay_0},none]#{?#(gitmux -cfg $HOME/.config/gitmux/gitmux.conf '#{pane_current_path}'),│,}"
+set -ga status-left "#{?#(gitmux -cfg $HOME/.config/gitmux/gitmux.conf '#{pane_current_path}'),#{#[bg=default,fg=#{@thm_subtext_0}] #(gitmux -cfg $HOME/.config/gitmux/gitmux.conf '#{pane_current_path}') }, }"
+set -ga status-left "#[bg=default,fg=#{@thm_overlay_0},none]#{?window_zoomed_flag,│,}"
+set -ga status-left "#[bg=default,fg=#{@thm_subtext_0}]#{?window_zoomed_flag,  zoom ,}"
+
+# status right style
+set -ogq @batt_icon_charge_tier8 "󰁹"
+set -ogq @batt_icon_charge_tier7 "󰂁"
+set -ogq @batt_icon_charge_tier6 "󰁿"
+set -ogq @batt_icon_charge_tier5 "󰁾"
+set -ogq @batt_icon_charge_tier4 "󰁽"
+set -ogq @batt_icon_charge_tier3 "󰁼"
+set -ogq @batt_icon_charge_tier2 "󰁻"
+set -ogq @batt_icon_charge_tier1 "󰁺"
+set -ogq @batt_icon_status_charged "󰚥"
+set -ogq @batt_icon_status_charging "󰂄"
+set -ogq @batt_icon_status_discharging "󰂃"
+set -ogq @batt_icon_status_unknown "󰂑"
+set -ogq @batt_icon_status_attached "󱈑"
+set -g status-right ""
+set -g status-right-length 90
+set -ga status-right "#{?#{pomodoro_status},#{pomodoro_status},#{#[bg=default,fg=#{@thm_peach}]  }}"
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-right "#[bg=default,fg=#{@thm_mauve}]  #{continuum_status} "
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none]│"
+# TODO: configure when the battery is available
+set -ga status-right "#{?#{<=:#{battery_percentage},0},#{#[bg=default,fg=#{@thm_red}] #{battery_icon} },#{#[bg=default,fg=#{@thm_green}]} #{battery_icon} #{battery_percentage} }"
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-right "#[bg=default]#{?#{==:#{online_status},ok},#[fg=#{@thm_pink}] 󰖩 ,#[fg=#{@thm_red},bold]#[reverse] 󰖪 }"
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-right "#[bg=default,fg=#{@thm_red}] #{weather} "
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-right "#[bg=default,fg=#{@thm_rosewater}] 󰔟 #(uptime | awk -F 'up ' '{print $2}' | awk -F ',' '{print $1}' | sed 's/:/h /;s/$/m/;s/^ //') "
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none]│"
+set -ga status-right "#[bg=default,fg=#{@thm_blue}] 󰭦 %a,%d 󰅐 %H:%M "
+set -ga status-right "#[bg=default,fg=#{@thm_overlay_0},none] "
+
+# pane border style
+setw -g pane-active-border-style "bg=#{@thm_bg},fg=#{@thm_overlay_0}"
+setw -g pane-border-format ""
+setw -g pane-border-lines single
+setw -g pane-border-status top # top or off
+setw -g pane-border-style "bg=#{@thm_bg},fg=#{@thm_surface_0}"
+
+# window style
+set -wg automatic-rename on
+set -g automatic-rename-format "Window" # Window or ""
+set -g window-status-format " #I#{?#{!=:#{window_name},Window},: #W,} "
+set -g window-status-style "bg=default,fg=#{@thm_rosewater}"
+set -g window-status-last-style "bg=#{@thm_crust},fg=#{@thm_peach}"
+set -g window-status-activity-style "bg=#{@thm_red},fg=#{@thm_surface_0}"
+set -g window-status-bell-style "bg=#{@thm_red},fg=#{@thm_surface_0},bold"
+set -g window-status-separator '|'
+set -g window-status-current-format " #I#{?#{!=:#{window_name},Window},: #W,} "
+set -g window-status-current-style "bg=#{@thm_peach},fg=#{@thm_surface_0},bold"
+
+
+### --- RUN TPM --- ###
+run '${TMUX_PLUGIN_MANAGER_PATH}/tpm/tpm'
diff --git a/ar/.config/transmission-daemon/settings.json b/ar/.config/transmission-daemon/settings.json
new file mode 100644
index 0000000..1137d23
--- /dev/null
+++ b/ar/.config/transmission-daemon/settings.json
@@ -0,0 +1,83 @@
+{
+ "alt-speed-down": 50,
+ "alt-speed-enabled": false,
+ "alt-speed-time-begin": 540,
+ "alt-speed-time-day": 127,
+ "alt-speed-time-enabled": false,
+ "alt-speed-time-end": 1020,
+ "alt-speed-up": 50,
+ "announce-ip": "",
+ "announce-ip-enabled": false,
+ "anti-brute-force-enabled": true,
+ "anti-brute-force-threshold": 100,
+ "bind-address-ipv4": "0.0.0.0",
+ "bind-address-ipv6": "::",
+ "blocklist-enabled": true,
+ "blocklist-url": "http://list.iblocklist.com/humantest?list=ydxerpxkpcfqjaybcssw&fileformat=p2p&archieveformat=gz",
+ "cache-size-mb": 16,
+ "default-trackers": "",
+ "dht-enabled": true,
+ "download-dir": "/home/si/Torrents/complete",
+ "download-queue-enabled": true,
+ "download-queue-size": 50,
+ "encryption": 1,
+ "idle-seeding-limit": 30,
+ "idle-seeding-limit-enabled": false,
+ "incomplete-dir": "/home/si/Torrents/incomplete",
+ "incomplete-dir-enabled": true,
+ "lpd-enabled": true,
+ "message-level": 4,
+ "peer-congestion-algorithm": "",
+ "peer-limit-global": 200,
+ "peer-limit-per-torrent": 100,
+ "peer-port": 51413,
+ "peer-port-random-high": 65535,
+ "peer-port-random-low": 49152,
+ "peer-port-random-on-start": false,
+ "peer-socket-tos": "le",
+ "pex-enabled": true,
+ "port-forwarding-enabled": true,
+ "preallocation": 1,
+ "prefetch-enabled": true,
+ "queue-stalled-enabled": true,
+ "queue-stalled-minutes": 30,
+ "ratio-limit": 2,
+ "ratio-limit-enabled": false,
+ "rename-partial-files": false,
+ "rpc-authentication-required": false,
+ "rpc-bind-address": "0.0.0.0",
+ "rpc-enabled": true,
+ "rpc-host-whitelist": "",
+ "rpc-host-whitelist-enabled": true,
+ "rpc-password": "{ea540a9e5ca1a548f3fa2eb9c4efc6cb43ed8e4141vvWF.X",
+ "rpc-port": 9091,
+ "rpc-socket-mode": "0750",
+ "rpc-url": "/transmission/",
+ "rpc-username": "",
+ "rpc-whitelist": "127.0.0.1,::1",
+ "rpc-whitelist-enabled": true,
+ "scrape-paused-torrents-enabled": true,
+ "script-torrent-added-enabled": false,
+ "script-torrent-added-filename": "",
+ "script-torrent-done-enabled": true,
+ "script-torrent-done-filename": "tordone",
+ "script-torrent-done-seeding-enabled": false,
+ "script-torrent-done-seeding-filename": "",
+ "seed-queue-enabled": true,
+ "seed-queue-size": 0,
+ "speed-limit-down": 100,
+ "speed-limit-down-enabled": false,
+ "speed-limit-up": 0,
+ "speed-limit-up-enabled": true,
+ "start-added-torrents": true,
+ "tcp-enabled": true,
+ "torrent-added-verify-mode": "fast",
+ "trash-can-enabled": true,
+ "trash-original-torrent-files": true,
+ "umask": "022",
+ "upload-slots-per-torrent": 0,
+ "utp-enabled": true,
+ "watch-dir": "/home/si/Torrents",
+ "watch-dir-enabled": true,
+ "watch-dir-force-generic": false
+}
diff --git a/ar/.config/user-dirs.dirs b/ar/.config/user-dirs.dirs
new file mode 100644
index 0000000..0c71f21
--- /dev/null
+++ b/ar/.config/user-dirs.dirs
@@ -0,0 +1,11 @@
+XDG_SCRIPTS_HOME="$HOME/.local/bin"
+XDG_SOURCES_HOME="$HOME/.local/src"
+XDG_DESKTOP_DIR="$HOME/Desktop"
+XDG_DOTFILES_DIR="$HOME/.dotfiles"
+XDG_DOCUMENTS_DIR="$HOME/Documents"
+XDG_DOWNLOAD_DIR="$HOME/Downloads"
+XDG_MUSIC_DIR="$HOME/Music"
+XDG_PICTURES_DIR="$HOME/Pictures"
+XDG_PUBLICSHARE_DIR="$HOME/Public"
+XDG_TEMPLATES_DIR="$HOME/Templates"
+XDG_VIDEOS_DIR="$HOME/Videos"
diff --git a/ar/.config/vim/UltiSnips/all.snippets b/ar/.config/vim/UltiSnips/all.snippets
new file mode 100644
index 0000000..cf55536
--- /dev/null
+++ b/ar/.config/vim/UltiSnips/all.snippets
@@ -0,0 +1,30 @@
+# place your snippets# symbols
+snippet .- "• " w
+•
+endsnippet
+
+snippet .v "✓" w
+✓
+endsnippet
+
+
+# functions
+snippet datek "Date (Korean format)" w
+`!v strftime("%Y.%m.%d")`
+endsnippet
+
+snippet datea "Date (American format)" w
+`!v strftime("%m.%d.%Y")`
+endsnippet
+
+snippet unix "Unix timestamp" w
+`date +%s`
+endsnippet
+
+snippet file "file name" w
+`!v expand('%:t')`
+endsnippet
+
+snippet path "absolute file path" w
+`!v expand('%:p')`
+endsnippet
diff --git a/ar/.config/vim/init.vim b/ar/.config/vim/init.vim
new file mode 100644
index 0000000..4147f08
--- /dev/null
+++ b/ar/.config/vim/init.vim
@@ -0,0 +1,430 @@
+
+" AUTOCMD ---------------------------------------------------------------- {{{
+
+" Close with q
+autocmd FileType checkhealth,help,lspinfo,neotest-output,neotest-output-panel,neotest-summary,netrw,notify,qf,query,spectre_panel,startuptime,terminal,tsplayground noremap <buffer> q :bd<CR>
+
+" Disables automatic commenting on newline:
+autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o
+
+" Nerd tree
+autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif
+
+" Runs a script that cleans out tex build files whenever I close out of a .tex file.
+autocmd VimLeave *.tex !texclear %
+
+" Text files
+let g:vimwiki_ext2syntax = {'.Rmd': 'markdown', '.rmd': 'markdown','.md': 'markdown', '.markdown': 'markdown', '.mdown': 'markdown'}
+let g:vimwiki_list = [{'path': '~/.local/share/nvim/vimwiki', 'syntax': 'markdown', 'ext': '.md'}]
+autocmd BufRead,BufNewFile /tmp/calcurse*,~/.calcurse/notes/* set filetype=markdown
+autocmd BufRead,BufNewFile *.ms,*.me,*.mom,*.man set filetype=groff
+autocmd BufRead,BufNewFile *.tex set filetype=tex
+
+" Enable Goyo by default for mutt writing
+autocmd BufRead,BufNewFile /tmp/neomutt* :Goyo 80 | call feedkeys("jk")
+autocmd BufRead,BufNewFile /tmp/neomutt* map ZZ :Goyo!\|x!<CR>
+autocmd BufRead,BufNewFile /tmp/neomutt* map ZQ :Goyo!\|q!<CR>
+
+" Automatically deletes all trailing whitespace and newlines at end of file on save. & reset cursor position
+autocmd BufWritePre * let currPos = getpos(".")
+autocmd BufWritePre * %s/\s\+$//e
+autocmd BufWritePre * %s/\n\+\%$//e
+autocmd BufWritePre *.[ch] %s/\%$/\r/e " add trailing newline for ANSI C standard
+autocmd BufWritePre *neomutt* %s/^--$/-- /e " dash-dash-space signature delimiter in emails
+autocmd BufWritePre * cal cursor(currPos[1], currPos[2])
+
+" When shortcut files are updated, renew bash and ranger configs with new material:
+autocmd BufWritePost bm-files,bm-dirs !shortcuts
+
+" Run xrdb whenever Xdefaults or Xresources are updated.
+autocmd BufRead,BufNewFile Xresources,Xdefaults,xresources,xdefaults set filetype=xdefaults
+autocmd BufWritePost Xresources,Xdefaults,xresources,xdefaults !xrdb %
+
+" Recompile dwmblocks on config edit.
+autocmd BufWritePost ~/.local/src/dwmblocks/config.h !cd ~/.local/src/dwmblocks/; sudo make install && { killall -q dwmblocks;setsid -f dwmblocks }
+
+" Which key description
+autocmd! User vim-which-key call which_key#register('<Space>', 'g:which_key_map')
+let g:which_key_map = {}
+
+" }}}
+
+
+" BACKUP ----------------------------------------------------------------- {{{
+
+if version >= 703
+ set undodir=~/.config/vim/undodir
+ set undofile
+ set undoreload=10000
+endif
+
+" }}}
+
+
+" PLUGINS INIT ----------------------------------------------------------- {{{
+
+if filereadable(expand("~/.config/vim/plugins.vim"))
+ silent !mkdir -p ${XDG_CONFIG_HOME:-$HOME/.config}/vim/plugged/
+ source ~/.config/vim/plugins.vim
+endif
+
+" goyo
+let g:is_goyo_active = v:false
+function! GoyoEnter()
+ if executable('tmux') && strlen($TMUX)
+ silent !tmux set status off
+ silent !tmux list-panes -F '\#F' | grep -q Z || tmux resize-pane -Z
+ endif
+
+ let g:default_colorscheme = exists('g:colors_name') ? g:colors_name : 'default'
+ set background=light
+ set linebreak
+ set wrap
+ set textwidth=0
+ set wrapmargin=0
+
+ Goyo 80x85%
+ colorscheme seoul256
+ let g:is_goyo_active = v:true
+endfunction
+
+function! GoyoLeave()
+ if executable('tmux') && strlen($TMUX)
+ silent !tmux set status on
+ silent !tmux list-panes -F '\#F' | grep -q Z && tmux resize-pane -Z
+ endif
+
+ Goyo!
+ execute 'colorscheme ' . g:default_colorscheme
+ let g:is_goyo_active = v:false
+endfunction
+
+function! ToggleGoyo()
+ if g:is_goyo_active
+ call GoyoLeave()
+ else
+ call GoyoEnter()
+ endif
+endfunction
+
+" }}}
+
+
+" PLUGIN MAPPINGS & SETTINGS -------------------------------------------------------- {{{
+
+" Open quickfix/location list
+let g:which_key_map.o = {
+ \ 'name' : '+Open' ,
+ \ 'q' : 'Quickfix-list' ,
+ \ 'l' : 'Location-list' ,
+ \ }
+
+" Check health
+nnoremap <Leader>ch :CheckHealth<CR>
+let g:which_key_map.c = { 'name' : 'Check' }
+let g:which_key_map.c.h = 'Check-health'
+
+" Bookmarks
+let g:bookmark_no_default_key_mappings = 1
+let g:bookmark_save_per_working_dir = 1
+let g:bookmark_auto_save = 1
+nmap <Leader>mm <Plug>BookmarkToggle
+nmap <Leader>mi <Plug>BookmarkAnnotate
+nmap <Leader>ma <Plug>BookmarkShowAll
+nmap <Leader>m] <Plug>BookmarkNext
+nmap <Leader>m[ <Plug>BookmarkPrev
+nmap <Leader>mc <Plug>BookmarkClear
+nmap <Leader>mx <Plug>BookmarkClearAll
+nmap <Leader>mk <Plug>BookmarkMoveUp
+nmap <Leader>mj <Plug>BookmarkMoveDown
+nmap <Leader>mg <Plug>BookmarkMoveToLine
+
+" Fugitive
+nnoremap <Leader>gs :Git<CR>
+let g:which_key_map.g = { 'name' : 'Git/Goyo' }
+let g:which_key_map.g.s = 'Git'
+
+" Goyo plugin makes text more readable when writing prose:
+nnoremap <Leader>gy :call ToggleGoyo()<CR>
+let g:which_key_map.g.y = 'Toggle-goyo'
+
+" Nerd tree
+map <Leader>n :NERDTreeToggle<CR>
+let g:which_key_map.n = 'Toggle-nerd-tree'
+
+" Undotree
+nnoremap <Leader>u :UndotreeToggle<CR>
+let g:which_key_map.u = 'Toggle-undo-tree'
+
+" vimwiki
+map <Leader>vw :VimwikiIndex<CR>
+let g:which_key_map.v = { 'name' : '+Vim-wiki' }
+let g:which_key_map.v.w = 'Vim-wiki-index'
+
+" vim-plug
+nnoremap <Leader>pc :PlugClean<CR>
+nnoremap <Leader>pi :PlugInstall<CR>
+nnoremap <Leader>pu :PlugUpdate<CR>
+let g:which_key_map.p = { 'name' : '+Plug' }
+let g:which_key_map.p.c = 'Plug-clean'
+let g:which_key_map.p.i = 'Plug-install'
+let g:which_key_map.p.u = 'Plug-update'
+
+" whichkey
+nnoremap <silent> <Leader> :<C-U>WhichKey '<Space>'<CR>
+nnoremap <silent> <localleader> :<C-U>WhichKey '\'<CR>
+
+" lsp
+if executable('pylsp')
+ " pip install python-lsp-server
+ au User lsp_setup call lsp#register_server({
+ \ 'name': 'pylsp',
+ \ 'cmd': {server_info->['pylsp']},
+ \ 'allowlist': ['python'],
+ \ })
+endif
+
+function! s:on_lsp_buffer_enabled() abort
+ setlocal omnifunc=lsp#complete
+ setlocal signcolumn=yes
+ if exists('+tagfunc') | setlocal tagfunc=lsp#tagfunc | endif
+ nmap <buffer> gd <plug>(lsp-definition)
+ nmap <buffer> gs <plug>(lsp-document-symbol-search)
+ nmap <buffer> gS <plug>(lsp-workspace-symbol-search)
+ nmap <buffer> gr <plug>(lsp-references)
+ nmap <buffer> gi <plug>(lsp-implementation)
+ nmap <buffer> gt <plug>(lsp-type-definition)
+ nmap <buffer> <Leader>lr <plug>(lsp-rename)
+ nmap <buffer> [t <plug>(lsp-previous-diagnostic)
+ nmap <buffer> ]t <plug>(lsp-next-diagnostic)
+ nmap <buffer> K <plug>(lsp-hover)
+ " nnoremap <buffer> <expr><C-D> lsp#scroll(+4)
+ " nnoremap <buffer> <expr><C-U> lsp#scroll(-4)
+
+ let g:lsp_format_sync_timeout = 1000
+ autocmd! BufWritePre *.rs,*.go,*.py call execute('LspDocumentFormatSync')
+
+ " refer to doc to add more commands
+endfunction
+
+let g:which_key_map.g = {
+ \ 'name' : '+Goto' ,
+ \ 'd' : 'Definition' ,
+ \ 's' : 'Symbol' ,
+ \ 'S' : 'Workspace-symbol' ,
+ \ 'r' : 'References' ,
+ \ 'i' : 'Implementation' ,
+ \ 't' : 'Type-definition' ,
+ \ }
+
+let g:which_key_map['['] = { 'name' : '+Previous' }
+let g:which_key_map[']'] = { 'name' : '+Next' }
+let g:which_key_map['[t'] = 'Diagnostic'
+let g:which_key_map[']t'] = 'Diagnostic'
+let g:which_key_map.K = 'Keyword'
+
+augroup lsp_install
+ au!
+ " call s:on_lsp_buffer_enabled only for languages that has the server registered.
+ autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
+augroup END
+
+let g:lsp_fold_enabled = 0
+let g:lsp_log_verbose = 1
+let g:lsp_log_file = expand('~/.cache/vim/vim-lsp.log')
+let g:asyncomplete_log_file = expand('~/.cache/vim/asyncomplete.log')
+let g:lsp_settings_filetype_python = ['pyright-langserver', 'ruff', 'ruff-lsp']
+
+nnoremap <Leader>li :LspInstallServer<CR>
+
+" vim-airline
+if !exists('g:airline_symbols')
+ let g:airline_symbols = {}
+endif
+let g:airline_symbols.colnr = ' C:'
+let g:airline_symbols.linenr = ' L:'
+let g:airline_symbols.maxlinenr = '☰ '
+
+" colorscheme
+if isdirectory(expand("~/.config/vim/plugged/catppuccin"))
+ let g:airline_theme = 'catppuccin_mocha'
+ colorscheme catppuccin_mocha
+endif
+
+" fzf
+let g:fzf_vim = {}
+let $FZF_DEFAULT_OPTS = "--layout=default --preview-window 'right:60%' --preview 'bat --style=numbers --line-range :300 {}'
+ \ --bind ctrl-y:preview-up,ctrl-e:preview-down,
+ \ctrl-b:preview-page-up,ctrl-f:preview-page-down,
+ \ctrl-u:preview-half-page-up,ctrl-d:preview-half-page-down,
+ \shift-up:preview-top,shift-down:preview-bottom,
+ \alt-up:half-page-up,alt-down:half-page-down"
+
+" tmux
+if exists('$TMUX')
+ let g:fzf_layout = { 'tmux': '90%,70%' }
+ let g:tmux_navigator_no_wrap = 1
+else
+ let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
+endif
+let g:fzf_vim.preview_window = ['right,50%,<70(up,40%)', 'ctrl-/']
+let g:fzf_vim.commits_log_options = '--graph --color=always --format="%C(auto)%h%d %s %C(black)%C(bold)%cr"'
+let g:fzf_vim.tags_command = 'ctags -R'
+
+function! s:build_quickfix_list(lines)
+ call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
+ copen
+ cc
+endfunction
+
+let g:fzf_action = {
+ \ 'ctrl-q' : function('s:build_quickfix_list'),
+ \ 'ctrl-t' : 'tab split' ,
+ \ 'ctrl-x' : 'split' ,
+ \ 'ctrl-v' : 'vsplit' ,
+ \ }
+
+nnoremap <Leader>cl :Colors<CR>
+nnoremap <Leader>fb :Files ~/.local/bin<CR>
+nnoremap <Leader>fc :Files ~/.config<CR>
+nnoremap <Leader>fd :Files ~/.dotfiles<CR>
+nnoremap <Leader>ff :Files .<CR>
+nnoremap <Leader>fF :Files ~<CR>
+nnoremap <Leader>fg :GFiles<CR>
+nnoremap <Leader>fG :GFiles?<CR>
+nnoremap <Leader>fs :Files ~/.local/src/suckless<CR>
+nnoremap <Leader>fv :Files ~/.config/vim<CR>
+nnoremap <Leader>sb :Buffers<CR>
+nnoremap <Leader>sc :Changes<CR>
+nnoremap <Leader>sC :Commands<CR>
+nnoremap <Leader>sg :Rg<CR>
+nnoremap <Leader>sG :RG<CR>
+nnoremap <Leader>shc :History:<CR>
+nnoremap <Leader>shh :History<CR>
+nnoremap <Leader>shp :Helptags<CR>
+nnoremap <Leader>shs :History/<CR>
+nnoremap <Leader>sj :Jumps<CR>
+nnoremap <Leader>sk :Maps<CR>
+nnoremap <Leader>sl :Locate<CR>
+nnoremap <Leader>sm :Marks<CR>
+nnoremap <Leader>sn :Snippets<CR>
+nnoremap <Leader>st :Filetypes<CR>
+nnoremap <Leader>gc :Commits<CR>
+nnoremap <Leader>gC :BCommits<CR>
+
+let g:which_key_map.c = 'Color-schemes'
+let g:which_key_map.f = {
+ \ 'name' : '+Find' ,
+ \ 'b' : 'Scripts' ,
+ \ 'c' : 'Config' ,
+ \ 'd' : 'Dotfiles' ,
+ \ 'f' : 'Files' ,
+ \ 'F' : 'Root-files' ,
+ \ 'g' : 'Git-files' ,
+ \ 'G' : 'Git-status' ,
+ \ 's' : 'Suckless' ,
+ \ 'v' : 'Vim-config' ,
+ \ }
+
+let g:which_key_map.g = {
+ \ 'name' : '+Git' ,
+ \ 'c' : 'Commits' ,
+ \ 'C' : 'Buffer-commits' ,
+ \ }
+
+let g:which_key_map.s = {
+ \ 'name' : '+Search' ,
+ \ 'b' : 'Buffers' ,
+ \ 'c' : 'Changes' ,
+ \ 'C' : 'Commands' ,
+ \ 'g' : 'Rip-grep' ,
+ \ 'G' : 'Rip-Grep' ,
+ \ 'h' : {
+ \ 'name' : '+History' ,
+ \ 'c' : 'Command-history' ,
+ \ 'h' : 'History' ,
+ \ 'p' : 'Help-tags' ,
+ \ 's' : 'Search-history' ,
+ \ },
+ \ 'j' : 'Jumps' ,
+ \ 'k' : 'Key-maps' ,
+ \ 'l' : 'Locate' ,
+ \ 'm' : 'Marks' ,
+ \ 'n' : 'Snippets' ,
+ \ 't' : 'File-types' ,
+ \ }
+
+
+" snippets
+let g:SuperTabDefaultCompletionType = '<C-N>'
+let g:SuperTabCrMapping = 0
+let g:UltiSnipsExpandTrigger = '<C-E>'
+let g:UltiSnipsJumpForwardTrigger = '<tab>'
+let g:UltiSnipsJumpBackwardTrigger = '<s-tab>'
+let g:UltiSnipsEditSplit = 'vertical'
+let g:UltiSnipsAutoTrigger = 1
+let g:asyncomplete_auto_completeopt = 0
+let g:asyncomplete_auto_popup = 1
+
+set completeopt=menuone,noinsert,noselect,preview
+autocmd! CompleteDone * if pumvisible() == 0 | pclose | endif
+
+if has('python3')
+ call asyncomplete#register_source(asyncomplete#sources#ultisnips#get_source_options({
+ \ 'name': 'ultisnips',
+ \ 'allowlist': ['*'],
+ \ 'completor': function('asyncomplete#sources#ultisnips#completor'),
+ \ }))
+endif
+
+inoremap <expr> <Tab> pumvisible() ? "\<C-N>" : "\<Tab>"
+inoremap <expr> <S-Tab> pumvisible() ? "\<C-P>" : "\<S-Tab>"
+inoremap <expr> <CR> pumvisible() ? asyncomplete#close_popup() : "\<CR>"
+
+" whichkey
+set timeoutlen=500
+
+let g:which_key_map.a = 'Select-all-the-text'
+let g:which_key_map.b = { 'name' : '+Buffer' }
+let g:which_key_map.b.n = 'New/open-buffer'
+let g:which_key_map.c = { 'name' : '+Format' }
+let g:which_key_map.c.f = 'Format-buffer'
+let g:which_key_map.e = 'Explorer'
+let g:which_key_map.h = { 'name' : '+Hex' }
+let g:which_key_map.h.x = 'Toggle-hex/reverse-conversion'
+let g:which_key_map.l = { 'name' : '+Lex/Lsp' }
+let g:which_key_map.l.e = 'Open-lex'
+let g:which_key_map.l.i = 'Lsp-install-server'
+let g:which_key_map.l.r = 'Rename'
+let g:which_key_map.o = { 'name' : '+Open' }
+let g:which_key_map.o.g = 'Orthography'
+let g:which_key_map.Q = 'Force-quit-all'
+let g:which_key_map.r = { 'name' : '+Replace' }
+let g:which_key_map.r.w = 'Replace word'
+let g:which_key_map.s = { 'name' : '+Surround' }
+let g:which_key_map.s.o = 'Source-file'
+let g:which_key_map.s.w = 'Surround-word'
+let g:which_key_map.t = 'Go-to-tab'
+let g:which_key_map["'"] = 'Register'
+let g:which_key_map['w'] = {
+ \ 'name' : '+windows' ,
+ \ 'd' : ['<C-W>c' , 'Delete-window'] ,
+ \ 'h' : ['<C-W>h' , 'Window-left'] ,
+ \ 'H' : ['<C-W>5<' , 'Expand-window-left'] ,
+ \ 'j' : ['<C-W>j' , 'Window-below'] ,
+ \ 'J' : [':resize +5' , 'Expand-window-below'] ,
+ \ 'k' : ['<C-W>k' , 'Window-up'] ,
+ \ 'K' : [':resize -5' , 'Expand-window-up'] ,
+ \ 'l' : ['<C-W>l' , 'Window-right'] ,
+ \ 'L' : ['<C-W>5>' , 'Expand-window-right'] ,
+ \ 's' : ['<C-W>s' , 'Split-window-below'] ,
+ \ 'v' : ['<C-W>v' , 'Split-window-below'] ,
+ \ 'w' : ['<C-W>w' , 'Other-window'] ,
+ \ '2' : ['<C-W>v' , 'Layout-double-columns'] ,
+ \ '-' : ['<C-W>s' , 'Split-window-below'] ,
+ \ '|' : ['<C-W>v' , 'Split-window-right'] ,
+ \ '=' : ['<C-W>=' , 'Balance-window'] ,
+ \ '?' : ['Windows' , 'Fzf-window'] ,
+ \ }
+
+" }}}
diff --git a/ar/.config/vim/plugins.vim b/ar/.config/vim/plugins.vim
new file mode 100644
index 0000000..072c4c2
--- /dev/null
+++ b/ar/.config/vim/plugins.vim
@@ -0,0 +1,42 @@
+if ! filereadable(system('echo -n "${XDG_CONFIG_HOME:-$HOME/.config}/vim/autoload/plug.vim"'))
+ echo "Downloading junegunn/vim-plug to manage plugins..."
+ silent !mkdir -p ${XDG_CONFIG_HOME:-$HOME/.config}/vim/autoload/
+ silent !mkdir -p ${XDG_CONFIG_HOME:-$HOME/.config}/vim/plugged/
+ silent !curl "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" > ${XDG_CONFIG_HOME:-$HOME/.config}/vim/autoload/plug.vim
+ autocmd VimEnter * PlugInstall
+endif
+
+call plug#begin(system('echo -n "${XDG_CONFIG_HOME:-$HOME/.config}/vim/plugged"'))
+Plug 'ap/vim-css-color'
+Plug 'catppuccin/vim', { 'as': 'catppuccin' }
+Plug 'christoomey/vim-tmux-navigator'
+Plug 'farmergreg/vim-lastplace'
+Plug 'honza/vim-snippets'
+Plug 'jiangmiao/auto-pairs'
+Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
+Plug 'junegunn/fzf.vim'
+Plug 'junegunn/goyo.vim'
+Plug 'junegunn/seoul256.vim'
+Plug 'kshenoy/vim-signature'
+Plug 'LutfiLokman/supertab'
+Plug 'liuchengxu/vim-which-key', { 'on': ['WhichKey', 'WhichKey!'] }
+Plug 'MattesGroeger/vim-bookmarks'
+Plug 'mattn/vim-lsp-settings'
+Plug 'mbbill/undotree', { 'on': 'UndotreeToggle' }
+Plug 'prabirshrestha/async.vim'
+Plug 'prabirshrestha/asyncomplete-ultisnips.vim'
+Plug 'prabirshrestha/asyncomplete.vim'
+Plug 'prabirshrestha/asyncomplete-lsp.vim'
+Plug 'prabirshrestha/vim-lsp'
+Plug 'preservim/nerdtree', { 'on': 'NERDTreeToggle' }
+Plug 'rhysd/vim-healthcheck'
+Plug 'SirVer/ultisnips'
+Plug 'thomasfaingnaert/vim-lsp-snippets'
+Plug 'thomasfaingnaert/vim-lsp-ultisnips'
+Plug 'tpope/vim-commentary'
+Plug 'tpope/vim-fugitive'
+Plug 'tpope/vim-repeat'
+Plug 'tpope/vim-surround'
+Plug 'vimwiki/vimwiki'
+Plug 'vim-airline/vim-airline'
+call plug#end()
diff --git a/ar/.config/vim/vimrc b/ar/.config/vim/vimrc
new file mode 100644
index 0000000..2148a42
--- /dev/null
+++ b/ar/.config/vim/vimrc
@@ -0,0 +1,586 @@
+
+" VIM ENV --------------------------------------------------------------- {{{
+
+if $USER != "root"
+ set runtimepath^=$XDG_CONFIG_HOME/vim
+ set runtimepath+=$XDG_DATA_HOME/vim
+ set runtimepath+=$XDG_CONFIG_HOME/vim/after
+ set viminfofile=$XDG_DATA_HOME/vim/.viminfo
+
+ set packpath^=$XDG_DATA_HOME/vim,$XDG_CONFIG_HOME/vim
+ set packpath+=$XDG_CONFIG_HOME/vim/after,$XDG_DATA_HOME/vim/after
+ let g:netrw_home = $XDG_DATA_HOME."/vim"
+ call mkdir($XDG_DATA_HOME."/vim/spell", 'p')
+
+ set backupdir=$XDG_CONFIG_HOME/vim/backup | call mkdir(&backupdir, 'p')
+ set directory=$XDG_CONFIG_HOME/vim/swap | call mkdir(&directory, 'p')
+ set undodir=$XDG_DATA_HOME/history/vim_history | call mkdir(&undodir, 'p')
+ set viewdir=$XDG_CONFIG_HOME/vim/view | call mkdir(&viewdir, 'p')
+else
+ set runtimepath^=/root/.config/vim
+ set runtimepath+=/root/.local/share/vim
+ set runtimepath+=/root/.config/vim/after
+ set viminfofile=/root/.local/share/vim/.viminfo
+
+ set packpath^=/root/.local/share/vim,/root/.config/vim
+ set packpath+=/root/.config/vim/after,/root/.local/share/vim/after
+
+ let g:netrw_home = "/root/.local/share/vim"
+ call mkdir("/root/.local/share/vim/spell", 'p')
+
+ set backupdir=/root/.config/vim/backup | call mkdir(&backupdir, 'p')
+ set directory=/root/.config/vim/swap | call mkdir(&directory, 'p')
+ set undodir=/root/.local/share/history/vim_history | call mkdir(&undodir, 'p')
+ set viewdir=/root/.config/vim/view | call mkdir(&viewdir, 'p')
+endif
+
+" }}}
+
+
+" AUTOCMD ---------------------------------------------------------------- {{{
+
+autocmd VimEnter * silent execute '!echo -ne "\e[2 q"'
+
+" }}}
+
+
+" Hex_Toggle_Functions --------------------------------------------------- {{{
+
+function! DoHex()
+ " Get the current buffer name
+ let current_file = expand('%')
+
+ " New file name
+ let new_file = current_file . '.hex'
+
+ " Save the current buffer as a hex file
+ execute 'w !xxd > ' . new_file
+
+ echo "Hex file created and saved as " . new_file
+endfunction
+
+function! UndoHex()
+ " Get the current buffer name
+ let current_file = expand('%')
+
+ " Name stage 1: Remove the .hex extension if it exists
+ let new_file_stage1 = substitute(current_file, '\.hex$', '', '')
+
+ " Get the file name without extension
+ let file_name = substitute(new_file_stage1, '\(.*\)\.\(\w\+\)$', '\1', '')
+
+ " Get the file extension
+ let file_extension = substitute(new_file_stage1, '\(.*\)\.\(\w\+\)$', '\2', '')
+
+ " Add 'M' before the extension(M = Modded)
+ let new_file = file_name . 'M.' . file_extension
+
+ " Save the current buffer as a reversed hex file
+ execute 'w !xxd -r > ' . new_file
+
+ echo "Reversed Hex file created and saved as " . new_file
+endfunction
+
+" Function to toggle between hex and original states
+function! HexState()
+ " Get user input to choose the operation (0 for DoHex, 1 for UndoHex)
+ let operation = input("Choose operation (0 for DoHex, 1 for UndoHex): ")
+
+ if operation == 0
+ " Execute the DoHex function
+ call DoHex()
+ elseif operation == 1
+ " Execute the UndoHex function
+ call UndoHex()
+ else
+ echo "Invalid choice. Aborting."
+ endif
+endfunction
+
+" }}}
+
+
+" GVIM - GUI VERSION ----------------------------------------------------- {{{
+
+if has('gui_running')
+
+ " " Set the color scheme.
+ " color desert
+
+ " Font
+ if has("macvim")
+ set guifont=Menlo\ Regular:h14
+ elseif has("win32")
+ set guifont=Consolas\ 14
+ else
+ set guifont=Consolas\ 18
+ endif
+
+ " Hide the toolbar.
+ set guioptions-=T
+
+ " Hide the right-side scroll bar.
+ set guioptions-=r
+
+ " Start Lex Tree and put the cursor back in the other window.
+ autocmd VimEnter * :Lexplore | wincmd p
+
+endif
+
+" }}}
+
+
+" MAPPINGS --------------------------------------------------------------- {{{
+
+" Set the space as the leader key.
+let mapleader = " "
+let maplocalleader = "\\"
+
+" Diable
+nnoremap Q <nop>
+
+" Highlight
+nnoremap <Esc> :noh<CR>
+
+" Cmd & Esc
+nnoremap <C-C> :
+inoremap <C-C> <Esc>:
+
+" Spell-check on\off to <Leader>o, 'o' for 'orthography':
+map <Leader>og :setlocal spell! spelllang=en_us<CR>
+
+" Type jk to exit insert mode quickly.
+inoremap jk <Esc>
+
+" Format a paragraph into lines
+map <Leader>cf gq<CR>
+
+" Select all the text
+nnoremap <Leader>a ggVG
+
+" Opening a file explore
+map <Leader>le :Lex<CR>
+
+" Opening a file from explorer
+map <Leader>e :Explore<CR>
+
+" Opening a terminal window
+map <C-T> :ter<CR>
+
+" Closing the terminal window
+tnoremap <C-T> exit<CR>
+
+" Buffer
+nnoremap H :bprev<CR>
+nnoremap L :bnext<CR>
+
+" CTRL+I OR Esc to make the terminal scrollable and I to input mode
+tnoremap <C-I> <C-W><S-N>
+tnoremap <Esc> <C-\><C-n>
+
+" You can split the window in Vim. y - in the y access , x - in the x access
+map <Leader>w\- :split<CR>
+map <Leader>w\| :vsplit<CR>
+
+" Better move
+nnoremap <C-U> 11kzz
+nnoremap <C-D> 11jzz
+nnoremap n nzzzv
+nnoremap N Nzzzv
+
+" Navigate the split view easier by pressing CTRL+j, CTRL+k, CTRL+h, or CTRL+l.
+nnoremap <C-J> <C-W>j
+nnoremap <C-K> <C-W>k
+nnoremap <C-H> <C-W>h
+nnoremap <C-L> <C-W>l
+
+" Resize split windows using arrow keys by pressing:
+" CTRL+UP, CTRL+DOWN, CTRL+LEFT, or CTRL+RIGHT.
+noremap <C-Up> <C-W>+
+noremap <C-Down> <C-W>-
+noremap <C-Left> <C-W><
+noremap <C-Right> <C-W>>
+
+" Moving between tabs
+map <Leader>t gt
+
+" Opening/Creating a file in a new tab - write the tab to open
+nnoremap <Leader>bn :tabedit<Space>
+
+" Saving a file using CTRL+S
+map <C-S> :w<CR>
+
+" Quitting and saving a file using CTRL+S
+map <Leader>bd :bd<CR>
+map <Leader>BD :bd!<CR>
+map <Leader>wq :wq<CR>
+nnoremap <Leader>q :q!<CR>
+nnoremap <Leader>Q :qa!<CR>
+
+" Surround word with a wanted character
+nnoremap <Leader>sw <CMD>echo "Press a character: " \| let c = nr2char(getchar()) \| exec "normal viwo\ei" . c . "\eea" . c . "\e" \| redraw<CR>
+
+" Replace all occurrences of a word
+nnoremap <Leader>rw :%s/\<<C-R><C-W>\>//g<Left><Left>
+
+" Toggle between creating a Hex conversion file and reversing the conversion
+nnoremap <Leader>hx <CMD>call HexState()<CR>
+
+" For copy and past if supports clipbard
+if has('gui_running')
+ map p "+P
+ xnoremap <Leader>p "_dP
+ nmap <Leader>Y "+Y
+ nnoremap <Leader>y "*y :let @+=@*<CR>
+ vnoremap <Leader>y "*y :let @+=@*<CR>
+ nmap <Leader>D "+D
+ nnoremap <Leader>d "+d
+ vnoremap <Leader>d "+d
+endif
+
+" Change
+nnoremap c "_c
+
+" Delete
+nnoremap x "_x
+
+" Seeing the registers
+nnoremap <Leader>' <CMD>registers<CR>
+
+" Moving lines in visual mode
+vnoremap J :m '>+1<CR>gv=gv
+vnoremap K :m '>-2<CR>gv=gv
+
+" Join
+nnoremap J mzJ`z
+
+" Perform dot commands over visual blocks:
+vnoremap . :normal .<CR>
+
+" Source file
+nnoremap <Leader>so :so<CR>
+
+" Compiler
+nnoremap <Leader>rr :w<CR>:terminal compiler %<CR>:resize 10<CR>
+
+" Open quickfix/location list"
+nnoremap <silent> <Leader>oq :copen<CR>
+nnoremap <silent> <Leader>ol :lopen<CR>
+
+" }}}
+
+
+" SETTINGS --------------------------------------------------------------- {{{
+
+" Cursor shapes
+let &t_SI = "\<Esc>[6 q"
+let &t_SR = "\<Esc>[4 q"
+let &t_EI = "\<Esc>[2 q"
+
+" Clipboard
+set clipboard+=unnamedplus
+
+" Disable auto commenting in a new line
+autocmd Filetype * setlocal formatoptions-=c formatoptions-=r formatoptions-=o
+
+" Setting the character encoding of Vim to UTF-8
+set encoding=UTF-8
+
+" Enable type file detection. Vim will be able to try to detect the type of file is use.
+filetype on
+
+" Enable spell check
+set nospell
+highlight SpellBad ctermfg=204 guifg=#F28FAD gui=undercurl guisp=#F28FAD
+highlight SpellCap ctermfg=75 guifg=#9D7CD8 gui=undercurl guisp=#9D7CD8
+highlight SpellRare ctermfg=81 guifg=#0DB9D7 gui=undercurl guisp=#0DB9D7
+highlight SpellLocal ctermfg=142 guifg=#FAB387 gui=undercurl guisp=#FAB387
+
+" Avoids updating the screen before commands are completed
+set lazyredraw
+
+" Smart tab
+set smarttab
+
+" Search down to subfolders
+set path+=**
+
+" Enable plugins and load plugin for the detected file type.
+filetype plugin on
+
+" Load an indent file for the detected file type.
+filetype indent on
+
+" Turn syntax highlighting on.
+syntax on
+
+" Turns off highlighting on the bits of code that are changed, so the line that is changed is highlighted but the actual text that has changed stands out on the line and is readable.
+if &diff
+ highlight! link DiffText MatchParen
+endif
+
+" Add numbers to the file.
+set number relativenumber
+
+" Mouse functionality
+set mouse=a
+
+" Background color
+" set bg=light
+
+" Color scheme
+" colorscheme desert
+
+" Highlight cursor line underneath the cursor horizontally.
+set cursorline
+
+" Disable highlight cursor line underneath the cursor vertically.
+set nocursorcolumn
+
+" Set shift width to 4 spaces.Set tab width to 4 columns.
+set shiftwidth=4
+set tabstop=4
+
+" If the current file type is HTML, set indentation to 2 spaces.
+autocmd Filetype html setlocal tabstop=2 shiftwidth=2 expandtab
+
+" Do not save backup files.
+set nobackup
+
+" Do wrap lines.
+set wrap
+
+" While searching though a file incrementally highlight matching characters as you type.
+set incsearch
+set hlsearch
+
+" Ignore capital letters during search.
+set ignorecase
+
+" Show partial command you type in the last line of the screen.
+set showcmd
+
+" Show the mode you are on the last line.
+set showmode
+
+" Show matching words during a search.
+set showmatch
+
+" Show title of the file
+set title
+
+" Timeout
+set timeoutlen=300 " Time (in milliseconds) to wait for a mapping
+set ttimeoutlen=10 " Time (in milliseconds) to wait for terminal key codes
+
+" Esc
+set noesckeys
+
+" Set the commands to save in history default number is 20.
+set history=1000
+
+" Setting the split window to open as i like (like in a WM - qtile)
+set splitbelow splitright
+
+" Enable auto completion menu after pressing TAB.
+set wildmenu
+
+" There are certain files that we would never want to edit with Vim.
+" Wild menu will ignore files with these extensions.
+set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx
+
+" If Vim version is equal to or greater than 7.3 enable undo file.
+" This allows you to undo changes to a file even after saving it.
+if version >= 703
+ set undodir=~/.config/vim/backup
+ set undofile
+ set undoreload=10000
+endif
+
+
+" File Browsing settings
+let g:netrw_banner=0
+let g:netrw_liststyle=3
+let g:netrw_showhide=1
+let g:netrw_winsize=20
+
+
+" Auto Completion - Enable Omni complete features
+set omnifunc=syntaxcomplete#Complete
+
+
+" Enable Spelling Suggestions for Auto-Completion:
+set complete+=k
+set completeopt=menu,menuone,noinsert
+
+
+" Minimalist-Tab Complete
+inoremap <expr> <Tab> TabComplete()
+fun! TabComplete()
+ if getline('.')[col('.') - 2] =~ '\K' || pumvisible()
+ return "\<C-N>"
+ else
+ return "\<Tab>"
+ endif
+endfun
+
+
+" Minimalist-Autocomplete
+inoremap <expr> <CR> pumvisible() ? "\<C-Y>" : "\<CR>"
+autocmd InsertCharPre * call AutoComplete()
+fun! AutoComplete()
+ if v:char =~ '\K'
+ \ && getline('.')[col('.') - 4] !~ '\K'
+ \ && getline('.')[col('.') - 3] =~ '\K'
+ \ && getline('.')[col('.') - 2] =~ '\K' " last char
+ \ && getline('.')[col('.') - 1] !~ '\K'
+
+ call feedkeys("\<C-N>", 'n')
+ end
+endfun
+
+
+" Closing compaction in insert mode
+inoremap [ []<Left>
+inoremap ( ()<Left>
+inoremap { {}<Left>
+inoremap /* /**/<Left><Left>
+
+" }}}
+
+
+" STATUS LINE ------------------------------------------------------------ {{{
+
+set laststatus=2
+set statusline=
+set statusline+=%2*
+set statusline+=%{StatuslineMode()}
+set statusline+=%{SpellCheckStatus()}
+set statusline+=%1*
+set statusline+=%3*
+set statusline+=<
+set statusline+=-
+set statusline+=%f
+set statusline+=-
+set statusline+=>
+set statusline+=%4*
+set statusline+=%m
+set statusline+=%=
+set statusline+=%h
+set statusline+=%r
+set statusline+=%4*
+set statusline+=%c
+set statusline+=/
+set statusline+=%l
+set statusline+=/
+set statusline+=%L
+set statusline+=%1*
+set statusline+=|
+set statusline+=%y
+set statusline+=%4*
+set statusline+=%P
+set statusline+=%3*
+set statusline+=t:
+set statusline+=%n
+
+" }}}
+
+
+" STATUS FUNCTIONS ------------------------------------------------------- {{{
+
+" Mode
+function! StatuslineMode()
+ let l:mode=mode()
+ if l:mode==#"n"
+ return "NORMAL"
+ elseif l:mode==#"V"
+ return "VISUAL LINE"
+ elseif l:mode==?"v"
+ return "VISUAL"
+ elseif l:mode==#"i"
+ return "INSERT"
+ elseif l:mode ==# "\<C-V>"
+ return "V-BLOCK"
+ elseif l:mode==#"R"
+ return "REPLACE"
+ elseif l:mode==?"s"
+ return "SELECT"
+ elseif l:mode==#"t"
+ return "TERMINAL"
+ elseif l:mode==#"c"
+ return "COMMAND"
+ elseif l:mode==#"!"
+ return "SHELL"
+ else
+ return "VIM"
+ endif
+endfunction
+
+" Spell Check Status
+function! SpellCheckStatus()
+ if &spell
+ return " [SPELL]"
+ else
+ return ''
+ endif
+endfunction
+" }}}
+
+
+" VIMSCRIPT -------------------------------------------------------------- {{{
+
+" This will enable code folding.
+" Use the marker method of folding.
+augroup filetype_vim
+ autocmd!
+ autocmd FileType vim setlocal foldmethod=marker
+augroup END
+
+
+
+" INIT VIM --------------------------------------------------------------- {{{
+
+if filereadable(expand("~/.config/vim/init.vim"))
+ source ~/.config/vim/init.vim
+endif
+
+" }}}
+
+
+" SHORTCUTS -------------------------------------------------------------- {{{
+
+if filereadable(expand("~/.config/vim/shortcuts.vim"))
+ silent! source ~/.config/vim/shortcuts.vim
+endif
+
+if filereadable(expand("~/.config/vim/rootshortcuts.vim"))
+ silent! source ~/.config/vim/rootshortcuts.vim
+endif
+
+" }}}
+
+
+" COLORS ----------------------------------------------------------------- {{{
+
+" Terminal color
+set t_Co=256
+if exists('+termguicolors')
+ set termguicolors
+endif
+
+" Cursor
+hi CursorLine cterm=NONE ctermbg=236 ctermfg=NONE gui=NONE guibg=Grey30 guifg=NONE
+
+" Transparent
+hi Normal ctermbg=NONE guibg=NONE
+hi NonText ctermbg=NONE guibg=NONE
+hi LineNr ctermbg=NONE guibg=NONE
+hi Folded ctermbg=NONE guibg=NONE
+hi EndOfBuffer ctermfg=Grey guifg=Grey
+
+hi User1 ctermbg=brown ctermfg=white guibg=black guifg=white
+hi User2 ctermbg=lightgreen ctermfg=black guibg=lightgreen guifg=black
+hi User3 ctermbg=brown ctermfg=lightcyan guibg=black guifg=lightblue
+hi User4 ctermbg=brown ctermfg=green guibg=black guifg=lightgreen
+
+" }}}
diff --git a/ar/.config/vscode/argv.json b/ar/.config/vscode/argv.json
new file mode 100644
index 0000000..766aeda
--- /dev/null
+++ b/ar/.config/vscode/argv.json
@@ -0,0 +1,21 @@
+// This configuration file allows you to pass permanent command line arguments to VS Code.
+// Only a subset of arguments is currently supported to reduce the likelihood of breaking
+// the installation.
+//
+// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT
+//
+// NOTE: Changing this file requires a restart of VS Code.
+{
+ // Use software rendering instead of hardware accelerated rendering.
+ // This can help in cases where you see rendering issues in VS Code.
+ // "disable-hardware-acceleration": true,
+
+ // Allows to disable crash reporting.
+ // Should restart the app if the value is changed.
+ "enable-crash-reporter": true,
+
+ // Unique id used for correlating crash reports sent from this instance.
+ // Do not edit this value.
+ "crash-reporter-id": "ab80ece9-6a06-484b-bd05-3861238d6615"
+}
+
diff --git a/ar/.config/wal/postrun b/ar/.config/wal/postrun
new file mode 100755
index 0000000..077969b
--- /dev/null
+++ b/ar/.config/wal/postrun
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+dunstconf="${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc"
+zathuraconf="${XDG_CONFIG_HOME:-$HOME/.config}/zathura/zathurarc"
+
+source "${XDG_CACHE_HOME:-$HOME/.cache}/wal/colors.sh"
+
+mkdir -p "${dunstconf%/*}" "${zathuraconf%/*}"
+
+mv -n "$dunstconf" "$dunstconf.bak"
+mv -n "$zathuraconf" "$zathuraconf.bak"
+
+ln -sf "${XDG_CACHE_HOME:-$HOME/.cache}/wal/dunstrc" "$dunstconf"
+ln -sf "${XDG_CACHE_HOME:-$HOME/.cache}/wal/zathurarc" "$zathuraconf"
+
+fix_sequences() {
+ e=$'\e'
+ sequences=$(cat)
+ foreground_color="$(echo -e "${sequences}\c" | grep --color=never -Eo "${e}]10[^${e}\\\\]*?${e}\\\\" | grep --color=never -Eo "#[0-9A-Fa-f]{6}")"
+ background_color="$(echo -e "${sequences}\c" | grep --color=never -Eo "${e}]11[^${e}\\\\]*?${e}\\\\" | grep --color=never -Eo "#[0-9A-Fa-f]{6}")"
+ cursor_color="$(echo -e "${sequences}\c" | grep --color=never -Eo "${e}]12[^${e}\\\\]*?${e}\\\\" | grep --color=never -Eo "#[0-9A-Fa-f]{6}")"
+
+ for term in /dev/pts/{0..9}*; do
+ echo -e "\e]4;256;${cursor_color}\a\c" >"${term}" 2>/dev/null
+ echo -e "\e]4;258;${background_color}\a\c" >"${term}" 2>/dev/null
+ echo -e "\e]4;259;${foreground_color}\a\c" >"${term}" 2>/dev/null
+ done
+}
+
+fix_sequences <"${XDG_CACHE_HOME:-$HOME/.cache}/wal/sequences"
+
+pkill dunst
+setsid -f dunst
diff --git a/ar/.config/wal/templates/dunstrc b/ar/.config/wal/templates/dunstrc
new file mode 100644
index 0000000..1768226
--- /dev/null
+++ b/ar/.config/wal/templates/dunstrc
@@ -0,0 +1,27 @@
+[global]
+ monitor = 0
+ follow = keyboard
+ width = 370
+ height = 350
+ offset = 0x19
+ padding = 2
+ horizontal_padding = 2
+ transparency = 25
+ font = Monospace 12
+ format = "<b>%s</b>\n%b"
+
+[urgency_low]
+ background = "{color0}" # color0
+ foreground = "{color8}" # color8
+ timeout = 3
+
+[urgency_normal]
+ foreground = "{color15}" # color15
+ background = "{color4}" # color4
+ timeout = 5
+
+[urgency_critical]
+ background = "{color1}" # color1
+ foreground = "{color15}" # color15
+ frame_color = "{color11}" # color11
+ timeout = 10
diff --git a/ar/.config/wal/templates/zathurarc b/ar/.config/wal/templates/zathurarc
new file mode 100644
index 0000000..66527a8
--- /dev/null
+++ b/ar/.config/wal/templates/zathurarc
@@ -0,0 +1,46 @@
+set sandbox none
+set statusbar-h-padding 0
+set statusbar-v-padding 0
+set page-padding 1
+set selection-clipboard clipboard
+map u scroll half-up
+map d scroll half-down
+map D toggle_page_mode
+map r reload
+map R rotate
+map K zoom in
+map J zoom out
+map i recolor
+map p print
+map g goto top
+map [fullscreen] u scroll half-up
+map [fullscreen] d scroll half-down
+map [fullscreen] D toggle_page_mode
+map [fullscreen] r reload
+map [fullscreen] R rotate
+map [fullscreen] K zoom in
+map [fullscreen] J zoom out
+map [fullscreen] i recolor
+map [fullscreen] p print
+map [fullscreen] g goto top
+
+set default-bg "{background}"
+set default-fg "{foreground}"
+set statusbar-bg "{background}"
+set statusbar-fg "{foreground}"
+set inputbar-bg "{background}"
+set inputbar-fg "{foreground}"
+set notification-bg "{background}"
+set notification-fg "{foreground}"
+set notification-error-bg "{background}"
+set notification-error-fg "{foreground}"
+set notification-warning-bg "{background}"
+set notification-warning-fg "{foreground}"
+set highlight-color "{color3}"
+set highlight-active-color "{color4}"
+set completion-bg "{color3}"
+set completion-fg "{color4}"
+set completion-highlight-bg "{color3}"
+set completion-highlight-fg "{color4}"
+set recolor-lightcolor "{background}"
+set recolor-darkcolor "{foreground}"
diff --git a/ar/.config/wget/wgetrc b/ar/.config/wget/wgetrc
new file mode 100644
index 0000000..97bba88
--- /dev/null
+++ b/ar/.config/wget/wgetrc
@@ -0,0 +1 @@
+hsts-file = ~/.cache/wget-hsts
diff --git a/ar/.config/x11/xinitrc b/ar/.config/x11/xinitrc
new file mode 100644
index 0000000..fe1bb35
--- /dev/null
+++ b/ar/.config/x11/xinitrc
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# xinitrc runs automatically when you run startx.
+
+# There are some small but important commands that need to be run when we start
+# the graphical environment. There is a link to this file in ~/.xprofile
+# because that file is run automatically if someone uses a display manager
+# (login screen) and so they are needed there. To prevent doubling up commands,
+# I source them here with the line below.
+
+if [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/x11/xprofile" ]; then
+ . "${XDG_CONFIG_HOME:-${HOME}/.config}/x11/xprofile"
+else
+ . "$HOME/.xprofile"
+fi
+
+# Activate dbus variables
+dbus-update-activation-environment --all
+dbus-launch ssh-agent dwm
diff --git a/ar/.config/x11/xprofile b/ar/.config/x11/xprofile
new file mode 100644
index 0000000..9d67fe3
--- /dev/null
+++ b/ar/.config/x11/xprofile
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+xrandr --dpi 96 # adjust the number if the resolution is higher than 1920x1080 or monitor size is bigger than 24"
+default="--mode 1920x1080 --rotate normal --scale 1.0x1.0 --dpi 96"
+for connected in $(xrandr -q | grep "\sconnected" | awk '{print $1}'); do
+ case $connected in
+ eDP*) edp="$connected" ;;
+ HDMI*) hdmi="$connected" ;;
+ DP*) dp="$connected" ;;
+ *) display="$connected" ;;
+ esac
+done
+if grep -q "disabled" /sys/class/drm/card0-eDP-1/enabled || grep -q "closed" /proc/acpi/button/lid/LID/state; then
+ # If the lid is closed, turn off the laptop's screen
+ if [ -n "$hdmi" ] && [ -z "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --off --output "$hdmi" --primary $default
+ elif [ -z "$hdmi" ] && [ -n "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --off --output "$dp" --primary $default
+ else
+ xrandr --output "$edp" --off --output "$display" --primary $default
+ fi
+else
+ # Apply display settings when lid is open
+ if [ -n "$hdmi" ] && [ -z "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --pos 1920x0 $default --output "$hdmi" --primary --pos 0x0 $default
+ elif [ -z "$hdmi" ] && [ -n "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --pos 1920x0 $default --output "$dp" --primary --pos 0x0 $default
+ elif [ -z "$hdmi" ] && [ -z "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --primary $default
+ else
+ xrandr --output "$display" --primary --auto
+ fi
+fi
+
+setbg & # set the background with the `setbg` script
+# fcitx5 & # set the input method, but it will be separate with fcitx5-remote
+
+# Uncomment to use Xresources colors/settings on startup
+# xrdb "${XDG_CONFIG_HOME:-${HOME}/.config}/x11/xresources" &
+# xrdbpid=$!
+
+autostart="mpd xcompmgr dunst unclutter pipewire remapd"
+
+for program in $autostart; do
+ pidof -sx "$program" || "$program" &
+done >/dev/null 2>&1
+
+# Ensure that xrdb has finished running before moving on to start the WM/DE.
+[ -n "$xrdbpid" ] && wait "$xrdbpid"
diff --git a/ar/.config/x11/xresources b/ar/.config/x11/xresources
new file mode 100644
index 0000000..6f99d20
--- /dev/null
+++ b/ar/.config/x11/xresources
@@ -0,0 +1,386 @@
+!! Transparency (0-1):
+*.alpha: 0.8
+
+!! Set a default font and font size as below:
+/* *.font: monospace:size=10 */
+
+/* name dark light */
+/* black 0 8 */
+/* red 1 9 */
+/* green 2 10 */
+/* yellow 3 11 */
+/* blue 4 12 */
+/* purple 5 13 */
+/* cyan 6 14 */
+/* white 7 15 */
+
+/* ! base16 dark */
+! *.foreground: #d8d8d8
+! *.background: #181818
+! *.cursorcolor: #d8d8d8
+! *.color0: #181818
+! *.color1: #ab4642
+! *.color2: #a1b56c
+! *.color3: #f7ca88
+! *.color4: #7cafc2
+! *.color5: #ba8baf
+! *.color6: #86c1b9
+! *.color7: #d8d8d8
+! *.color8: #585858
+! *.color9: #ab4642
+! *.color10: #a1b56c
+! *.color11: #f7ca88
+! *.color12: #7cafc2
+! *.color13: #ba8baf
+! *.color14: #86c1b9
+! *.color15: #f8f8f8
+
+/* !! brogrammer: */
+! *.foreground: #d6dbe5
+! *.background: #131313
+! *.cursorcolor: #b9b9b9
+! *.colorbd: #d6dbe5
+! *.color0: #1f1f1f
+! *.color8: #d6dbe5
+! *.color1: #f81118
+! *.color9: #de352e
+! *.color2: #2dc55e
+! *.color10: #1dd361
+! *.color3: #ecba0f
+! *.color11: #f3bd09
+! *.color4: #2a84d2
+! *.color12: #1081d6
+! *.color5: #4e5ab7
+! *.color13: #5350b9
+! *.color6: #1081d6
+! *.color14: #0f7ddb
+! *.color7: #d6dbe5
+! *.color15: #ffffff
+! *.colorbd: #d6dbe5
+
+/* !! catppuccin frappe: */
+! *.background: #303446
+! *.foreground: #c6d0f5
+! *.cursorColor: #f2d5cf
+! *.color0: #51576d
+! *.color1: #e78284
+! *.color2: #a6d189
+! *.color3: #e5c890
+! *.color4: #8caaee
+! *.color5: #f4b8e4
+! *.color6: #81c8be
+! *.color7: #b5bfe2
+! *.color8: #626880
+! *.color9: #e78284
+! *.color10: #a6d189
+! *.color11: #e5c890
+! *.color12: #8caaee
+! *.color13: #f4b8e4
+! *.color14: #81c8be
+! *.color15: #a5adce
+
+/* !! catppuccin latte: */
+! *.background: #eff1f5
+! *.foreground: #4c4f69
+! *.cursorColor: #dc8a78
+! *.color0: #5c5f77
+! *.color1: #d20f39
+! *.color2: #40a02b
+! *.color3: #df8e1d
+! *.color4: #1e66f5
+! *.color5: #ea76cb
+! *.color6: #179299
+! *.color7: #acb0be
+! *.color8: #6c6f85
+! *.color9: #d20f39
+! *.color10: #40a02b
+! *.color11: #df8e1d
+! *.color12: #1e66f5
+! *.color13: #ea76cb
+! *.color14: #179299
+! *.color15: #bcc0cc
+
+/* !! catppuccin macchiato: */
+! *.background: #24273a
+! *.foreground: #cad3f5
+! *.cursorColor: #f4dbd6
+! *.color0: #494d64
+! *.color1: #ed8796
+! *.color2: #a6da95
+! *.color3: #eed49f
+! *.color4: #8aadf4
+! *.color5: #f5bde6
+! *.color6: #8bd5ca
+! *.color7: #b8c0e0
+! *.color8: #5b6078
+! *.color9: #ed8796
+! *.color10: #a6da95
+! *.color11: #eed49f
+! *.color12: #8aadf4
+! *.color13: #f5bde6
+! *.color14: #8bd5ca
+! *.color15: #a5adcb
+
+/* !! catppuccin mocha: */
+! *.background: #1e1e2e
+! *.foreground: #cdd6f4
+! *.cursorColor: #f5e0dc
+! *.color0: #45475a
+! *.color1: #f38ba8
+! *.color2: #a6e3a1
+! *.color3: #f9e2af
+! *.color4: #89b4fa
+! *.color5: #f5c2e7
+! *.color6: #94e2d5
+! *.color7: #bac2de
+! *.color8: #585b70
+! *.color9: #f38ba8
+! *.color10: #a6e3a1
+! *.color11: #f9e2af
+! *.color12: #89b4fa
+! *.color13: #f5c2e7
+! *.color14: #94e2d5
+! *.color15: #a6adc8
+
+/* ! dracula */
+! *.foreground: #F8F8F2
+! *.background: #282A36
+! *.cursorColor: #bbbbbb
+! *.color0: #000000
+! *.color1: #FF5555
+! *.color2: #50FA7B
+! *.color3: #F1FA8C
+! *.color4: #BD93F9
+! *.color5: #FF79C6
+! *.color6: #8BE9FD
+! *.color7: #BFBFBF
+! *.color8: #4D4D4D
+! *.color9: #FF6E67
+! *.color10: #5AF78E
+! *.color11: #F4F99D
+! *.color12: #CAA9FA
+! *.color13: #FF92D0
+! *.color14: #9AEDFE
+! *.color15: #E6E6E6
+
+/* !! gruvbox dark */
+! *background: #282828
+! *foreground: #ebdbb2
+! *color0: #282828
+! *color8: #928374
+! *color1: #cc241d
+! *color9: #fb4934
+! *color2: #98971a
+! *color10: #b8bb26
+! *color3: #d79921
+! *color11: #fabd2f
+! *color4: #458588
+! *color12: #83a598
+! *color5: #b16286
+! *color13: #d3869b
+! *color6: #689d6a
+! *color14: #8ec07c
+! *color7: #a89984
+! *color15: #ebdbb2
+! *.color256: #1d2021
+! *.color257: #EBDBB2
+! *.color258: #32302f
+
+/* !! gruvbox light: */
+! *.background: #fbf1c7
+! *.foreground: #3c3836
+! *.color0: #fdf4c1
+! *.color1: #cc241d
+! *.color2: #98971a
+! *.color3: #d79921
+! *.color4: #458588
+! *.color5: #b16286
+! *.color6: #689d6a
+! *.color7: #7c6f64
+! *.color8: #928374
+! *.color9: #9d0006
+! *.color10: #79740e
+! *.color11: #b57614
+! *.color12: #076678
+! *.color13: #8f3f71
+! *.color14: #427b58
+! *.color15: #3c3836
+! *.color256: #f9f5d7
+! *.color257: #282828
+! *.color258: #f2e5bc
+
+/* !! one dark */
+! *.foreground: #ABB2BF
+! *.background: #1E2127
+! *.cursorColor: #5C6370
+! *.highlightColor:#3A3F4B
+! *.color0: #1E2127
+! *.color1: #E06C75
+! *.color2: #98C379
+! *.color3: #D19A66
+! *.color4: #61AFEF
+! *.color5: #C678DD
+! *.color6: #56B6C2
+! *.color7: #ABB2BF
+! *.color8: #5C6370
+! *.color9: #E06C75
+! *.color10: #98C379
+! *.color11: #D19A66
+! *.color12: #61AFEF
+! *.color13: #C678DD
+! *.color14: #56B6C2
+! *.color15: #FFFFFF
+
+/* !! one half dark */
+! *.foreground: #dcdfe4
+! *.background: #282c34
+! *.cursorColor: #a3b3cc
+! *.color0: #282c34
+! *.color1: #e06c75
+! *.color2: #98c379
+! *.color3: #e5c07b
+! *.color4: #61afef
+! *.color5: #c678dd
+! *.color6: #56b6c2
+! *.color7: #dcdfe4
+! *.color8: #282c34
+! *.color9: #e06c75
+! *.color10: #98c379
+! *.color11: #e5c07b
+! *.color12: #61afef
+! *.color13: #c678dd
+! *.color14: #56b6c2
+! *.color15: #dcdfe4
+
+/* !! one half light */
+! *.foreground: #383a42
+! *.background: #fafafa
+! *.cursorColor: #bfceff
+! *.color0: #383a42
+! *.color1: #e45649
+! *.color2: #50a14f
+! *.color3: #c18401
+! *.color4: #0184bc
+! *.color5: #a626a4
+! *.color6: #0997b3
+! *.color7: #fafafa
+! *.color8: #4f525e
+! *.color9: #e06c75
+! *.color10: #98c379
+! *.color11: #e5c07b
+! *.color12: #61afef
+! *.color13: #c678dd
+! *.color14: #56b6c2
+! *.color15: #ffffff
+
+/* !! solarized dark */
+! *.foreground: #93a1a1
+! *.background: #002b36
+! *.cursorcolor: #93a1a1
+! *.color0: #073642
+! *.color1: #dc322f
+! *.color2: #859900
+! *.color3: #b58900
+! *.color4: #268bd2
+! *.color5: #d33682
+! *.color6: #2aa198
+! *.color7: #eee8d5
+! *.color9: #cb4b16
+! *.color8: #fdf6e3
+! *.color10: #586e75
+! *.color11: #657b83
+! *.color12: #839496
+! *.color13: #6c71c4
+! *.color14: #93a1a1
+! *.color15: #fdf6e3
+
+/* !! nord */
+! *.foreground: #D8DEE9
+! *.background: #2E3440
+! *.cursorColor: #D8DEE9
+! *.color0 #2E3440
+! *.color1 #3B4252
+! *.color2 #434C5E
+! *.color3 #4C566A
+! *.color4 #D8DEE9
+! *.color5 #E5E9F0
+! *.color6 #ECEFF4
+! *.color7 #8FBCBB
+! *.color8 #88C0D0
+! *.color9 #81A1C1
+! *.color10 #5E81AC
+! *.color11 #BF616A
+! *.color12 #D08770
+! *.color13 #EBCB8B
+! *.color14 #A3BE8C
+! *.color15 #B48EAD
+
+/* !! xterm light */
+! *.foreground: #a8a8a8
+! *.background: #000000
+! *.color0: #000000
+! *.color1: #CD0000
+! *.color2: #00CD00
+! *.color3: #CDCD00
+! *.color4: #0000CD
+! *.color5: #CD00CD
+! *.color6: #00CDCD
+! *.color7: #E5E5E5
+! *.color8: #4D4D4D
+! *.color9: #FF0000
+! *.color10: #00FF00
+! *.color11: #FFFF00
+! *.color12: #0000FF
+! *.color13: #FF00FF
+! *.color14: #00FFFF
+! *.color15: #AABAC8
+
+/* !! xterm dark */
+! *.foreground: #a8a8a8
+! *.background: #000000
+! *.color0: #000000
+! *.color1: #a80000
+! *.color2: #00a800
+! *.color3: #a85400
+! *.color4: #0000a8
+! *.color5: #a800a8
+! *.color6: #00a8a8
+! *.color7: #a8a8a8
+! *.color8: #545454
+! *.color9: #fc5454
+! *.color10: #54fc54
+! *.color11: #fcfc54
+! *.color12: #5454fc
+! *.color13: #fc54fc
+! *.color14: #54fcfc
+! *.color15: #fcfcfc
+
+/* !! palette */
+/* bg: #1D2021 */
+/* fg: #CDD6F4 */
+/* bg: #1E1E2E */
+/* fg: #EBDBB2 */
+/* red: #F38BA8 */
+/* green: #A6E3A1 */
+/* yellow: #F9E2AF */
+/* peach: #FAB387 */
+/* blue: #89B4FA */
+/* maroon: #EBA0AC */
+/* teal: #94E2D5 */
+/* rosewater: #F5E0DC */
+/* flamingo: #F2CDCD */
+/* pink: #F5C2E7 */
+/* mauve: #CBA6F7 */
+/* sky: #89DCEB */
+/* sapphire: #74C7EC */
+/* lavender: #B4BEFE */
+/* subtext_1: #A6ADC8 */
+/* subtext_0: #BAC2DE */
+/* overlay_2: #9399B2 */
+/* overlay_1: #7F849C */
+/* overlay_0: #6C7086 */
+/* surface_2: #585B70 */
+/* surface_1: #45475A */
+/* surface_0: #313244 */
+/* mantle: #181825 */
+/* crust: #11111B */
diff --git a/ar/.config/youtube-viewer/youtube-viewer.conf b/ar/.config/youtube-viewer/youtube-viewer.conf
new file mode 100644
index 0000000..f7af0ee
--- /dev/null
+++ b/ar/.config/youtube-viewer/youtube-viewer.conf
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+# YouTube Viewer 3.11.4 - configuration file
+
+use utf8;
+
+our $CONFIG = {
+ audio_quality => "best",
+ auto_captions => 0,
+ autolike_watched => 0,
+ autoplay_mode => 0,
+ bypass_age_gate_native => 0,
+ bypass_age_gate_with_proxy => 0,
+ cache_dir => "$ENV{HOME}/.cache/youtube-viewer",
+ colors => 1,
+ comments_order => "time",
+ confirm => 0,
+ convert_cmd => "ffmpeg -i *IN* *OUT*",
+ convert_to => undef,
+ cookie_file => undef,
+ copy_caption => 0,
+ custom_channel_layout_format => [
+ { align => "right", color => "bold", text => "*NO*.", width => 3 },
+ { align => "left", color => "bold blue", text => "*TITLE*", width => "55%" },
+ { align => "right", color => "yellow", text => "*AGE_SHORT*", width => 3 },
+ {
+ align => "right",
+ color => "magenta",
+ text => "*VIDEOS* videos",
+ width => 14,
+ },
+ {
+ align => "right",
+ color => "green",
+ text => "*SUBS_SHORT* subs",
+ width => 10,
+ },
+ ],
+ custom_layout_format => [
+ { align => "right", color => "bold", text => "*NO*.", width => 3 },
+ { align => "left", color => "bold blue", text => "*TITLE*", width => "55%" },
+ { align => "left", color => "magenta", text => "*AUTHOR*", width => "15%" },
+ { align => "right", color => "green", text => "*AGE_SHORT*", width => 3 },
+ { align => "right", color => "green", text => "*VIEWS_SHORT*", width => 5 },
+ { align => "right", color => "blue", text => "*TIME*", width => 8 },
+ ],
+ custom_playlist_layout_format => [
+ { align => "right", color => "bold", text => "*NO*.", width => 3 },
+ { align => "left", color => "bold blue", text => "*TITLE*", width => "55%" },
+ { align => "right", color => "green", text => "*ITEMS* videos", width => 14 },
+ { align => "left", color => "magenta", text => "*AUTHOR*", width => "20%" },
+ ],
+ dash_segmented => 1,
+ debug => 0,
+ download_and_play => 0,
+ download_in_subdir => 0,
+ download_in_subdir_format => "*AUTHOR*",
+ download_with_wget => 1,
+ download_with_ytdl => 1,
+ downloads_dir => ".",
+ env_proxy => 1,
+ fat32safe => 0,
+ ffmpeg_cmd => "/usr/bin/ffmpeg",
+ force_fallback => 0,
+ fullscreen => 0,
+ get_captions => 1,
+ get_term_width => 1,
+ hfr => 1,
+ highlight_color => "bold",
+ highlight_watched => 1,
+ history => 1,
+ history_file => "$ENV{HOME}/.local/share/history/youtube_viewer_history.txt",
+ history_limit => 100000,
+ hl => "en_US",
+ http_proxy => undef,
+ ignore_av1 => 0,
+ ignored_projections => [],
+ interactive => 1,
+ keep_original_video => 0,
+ maxResults => 20,
+ merge_into_mkv => 1,
+ merge_into_mkv_args => "-loglevel warning -c:s srt -c:v copy -c:a copy -disposition:s forced",
+ merge_with_captions => 1,
+ order => undef,
+ page => 1,
+ prefer_av1 => 0,
+ prefer_m4a => 0,
+ prefer_mp4 => 0,
+ publishedAfter => undef,
+ publishedBefore => undef,
+ regionCode => undef,
+ remove_played_file => 0,
+ resolution => "best",
+ safeSearch => undef,
+ saved_videos_file => "$ENV{HOME}/.config/youtube-viewer/playlists/saved_videos.txt",
+ set_mtime => 1,
+ show_video_info => 1,
+ skip_if_exists => 1,
+ skip_watched => 0,
+ split_videos => 1,
+ srt_languages => ["en", "es"],
+ subscription_videos_per_channel => 20,
+ subscriptions_order => "relevance",
+ thousand_separator => ",",
+ timeout => undef,
+ user_agent => undef,
+ video_filename_format => "*FTITLE* - *ID*.*FORMAT*",
+ video_min_seconds => 0,
+ video_player_selected => "mpv",
+ video_players => {
+ mpv => {
+ arg => "--really-quiet --force-media-title=*TITLE* --no-ytdl *VIDEO*",
+ audio => "--audio-file=*AUDIO*",
+ cmd => "/usr/bin/mpv",
+ fs => "--fullscreen",
+ novideo => "--no-video",
+ srt => "--sub-file=*SUB*",
+ },
+ vlc => {
+ arg => "--quiet --play-and-exit --no-video-title-show --input-title-format=*TITLE* *VIDEO*",
+ audio => "--input-slave=*AUDIO*",
+ cmd => "vlc",
+ fs => "--fullscreen",
+ novideo => "--intf=dummy --novideo",
+ srt => "--sub-file=*SUB*",
+ },
+ },
+ videoCaption => undef,
+ videoDefinition => undef,
+ videoDimension => undef,
+ videoDuration => undef,
+ videoLicense => undef,
+ videoSyndicated => undef,
+ watch_history => 1,
+ watch_history_file => "$ENV{HOME}/.local/share/history/youtube_viewer_watched_history.txt",
+ wget_cmd => "/usr/bin/wget",
+ youtube_users_file => "$ENV{HOME}/.config/youtube-viewer/youtube_users.txt",
+ youtube_video_url => "https://www.youtube.com/watch?v=%s",
+ ytdl => 1,
+ ytdl_cmd => "/usr/bin/yt-dlp",
+}
diff --git a/ar/.config/zsh/.zshrc b/ar/.config/zsh/.zshrc
new file mode 100644
index 0000000..2cbb3a6
--- /dev/null
+++ b/ar/.config/zsh/.zshrc
@@ -0,0 +1,114 @@
+#!/bin/zsh
+
+### --- Prompt --- ###
+autoload -U colors && colors
+autoload -Uz add-zsh-hook vcs_info
+setopt prompt_subst
+add-zsh-hook precmd vcs_info
+PROMPT='%B%{$fg[red]%}[%{$fg[yellow]%}%n%{$fg[green]%}@%{$fg[blue]%}%M %{$fg[magenta]%}%~%{$fg[red]%}]%F{green}${vcs_info_msg_0_}%{$reset_color%}$%b '
+zstyle ':vcs_info:*' enable git
+zstyle ':vcs_info:*' check-for-changes true
+zstyle ':vcs_info:*' unstagedstr '*'
+zstyle ':vcs_info:*' stagedstr '+'
+zstyle ':vcs_info:git:*' formats "%{$fg[blue]%}(%{$fg[black]%}%b%{$fg[blue]%}:%r%{$fg[yellow]%}%u%m%{$fg[magenta]%}%c%{$fg[blue]%})"
+zstyle ':vcs_info:git:*' actionformats "%{$fg[blue]%}(%{$fg[black]%}%b%{$fg[blue]%}:%r%{$reset_color%}|%{$fg[red]%}%a%u%c%{$fg[blue]%})"
+zstyle ':vcs_info:git*+set-message:*' hooks git-untracked git-behind-upstream git-ahead-upstream git-diverged-upstream
++vi-git-untracked() {
+ if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == "true" ]] && \
+ git status --porcelain | grep -m 1 "^??" &>/dev/null
+ then
+ hook_com[misc]+="%{$fg[yellow]%}%%"
+ fi
+}
++vi-git-behind-upstream() {
+ if [[ $(git rev-list HEAD..$(git rev-parse --abbrev-ref @{upstream}) --count) -gt 0 ]]; then
+ hook_com[misc]+="%{$fg[red]%}<"
+ fi
+}
++vi-git-ahead-upstream() {
+ if [[ $(git rev-list $(git rev-parse --abbrev-ref @{upstream})..HEAD --count) -gt 0 ]]; then
+ hook_com[misc]+="%{$fg[green]%}>"
+ fi
+}
++vi-git-diverged-upstream() {
+ local ahead_count=$(git rev-list --count $(git rev-parse --abbrev-ref @{upstream})..HEAD 2>/dev/null)
+ local behind_count=$(git rev-list --count HEAD..$(git rev-parse --abbrev-ref @{upstream}) 2>/dev/null)
+ if [[ "$ahead_count" -gt 0 && "$behind_count" -gt 0 ]]; then
+ hook_com[misc]+="%{$fg[white]%}<>"
+ fi
+}
+
+
+### --- ZSH --- ###
+# GnuPG
+unset SSH_AGENT_PID
+if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
+ export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
+ gpgconf --launch gpg-agent
+fi
+export GPG_TTY="$(tty)"
+gpg-connect-agent updatestartuptty /bye >/dev/null
+
+# Options
+stty stop undef # Disable ctrl-s to freeze terminal.
+setopt autocd
+setopt extendedglob
+setopt nomatch
+setopt menucomplete
+setopt interactive_comments
+unsetopt bad_pattern
+
+# History in cache directory
+HISTSIZE=10000000
+SAVEHIST=10000000
+HISTFILE="${XDG_DATA_HOME:-${HOME}/.local/share}/history/sh_history"
+setopt inc_append_history
+setopt appendhistory
+setopt share_history
+setopt hist_ignore_all_dups
+setopt hist_ignore_space # ignores all commands starting with a blank space! Usefull for passwords
+
+# Style
+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS+=(vi-forward-char forward-char)
+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS:#(vi-forward-char|forward-char)})
+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=60'
+zstyle :bracketed-paste-magic paste-init pasteinit
+zstyle :bracketed-paste-magic paste-finish pastefinish
+
+
+### --- Autoload compinit and run it --- ###
+autoload -Uz compinit # Autoload compinit
+_comp_options+=(globdots) # Include hidden files in completion
+compinit # Initialize completion system
+zmodload zsh/complist # Load completion list module
+zmodload -i zsh/parameter # Load last command output
+
+# _dotbare_completion_cmd
+zstyle ':completion:*' menu select # selectable menu
+zstyle ':completion:*' matcher-list 'm:{[:lower:][:upper:]-_}={[:upper:][:lower:]_-}' 'r:|=*' 'l:|=* r:|=*' # case insensitive completion
+zstyle ':completion:*' special-dirs true # Complete . and .. special directories
+zstyle ':completion:*' list-colors '' # colorize completion lists
+zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01' # colorize kill list
+
+# fzf-tab
+zstyle ':completion:*:git-checkout:*' sort false # disable sort when completing `git checkout`
+zstyle ':completion:*:descriptions' format '[%d]' # set descriptions format to enable group support
+zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS} # set list-colors to enable filename colorizing
+zstyle ':fzf-tab:complete:cd:*' fzf-preview 'exa -1 --color=always $realpath' # preview directory's content with exa when completing cd
+zstyle ':fzf-tab:*' switch-group ',' '.' # switch group using `,` and `.`
+
+
+### --- Load ZSH Configs, Aliases, Functions, and Shortcuts --- ###
+# NOTE: the sequence of sourcing files is strict. Be careful to change the sequence.
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/git.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/git.zsh"
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/p10k.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/p10k.zsh"
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/autocomplete.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/autocomplete.zsh"
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/scripts.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/scripts.zsh"
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/keymaps.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/keymaps.zsh"
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/plugins.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/plugins.zsh"
+[ -f "${ZDOTDIR:-${HOME}/.config/zsh}/packages.zsh" ] && source "${ZDOTDIR:-${HOME}/.config/zsh}/packages.zsh"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/aliasrc" ] && source "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/aliasrc"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/git-aliasrc" ] && source "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/git-aliasrc"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutrc" ] && source "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutrc"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutenvrc" ] && source "${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutenvrc"
+[ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/zshnameddirrc" ] && source "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/zshnameddirrc"
diff --git a/ar/.config/zsh/autocomplete.zsh b/ar/.config/zsh/autocomplete.zsh
new file mode 100644
index 0000000..4763f71
--- /dev/null
+++ b/ar/.config/zsh/autocomplete.zsh
@@ -0,0 +1,70 @@
+### --- Auto-completes aliases --- ###
+# alias - normal aliases (completed with trailing space)
+# balias - blank aliases (completed without space)
+# ialias - ignored aliases (not completed)
+
+
+# ignored aliases
+typeset -a ialiases
+ialiases=()
+
+ialias() {
+ alias $@
+ args="$@"
+ args=${args%%\=*}
+ ialiases+=(${args##* })
+}
+
+
+# blank aliases
+typeset -a baliases
+baliases=()
+
+balias() {
+ alias $@
+ args="$@"
+ args=${args%%\=*}
+ baliases+=(${args##* })
+}
+
+
+# functionality
+expand-alias-space() {
+ [[ $LBUFFER =~ "\<(${(j:|:)baliases})\$" ]] && insertBlank=$?
+ if [[ ! $LBUFFER =~ "\<(${(j:|:)ialiases})\$" ]]; then
+ zle _expand_alias
+ zle expand-word
+ fi
+ zle self-insert
+ if [[ "$insertBlank" -eq 0 ]]; then
+ zle backward-delete-char
+ fi
+}
+zle -N expand-alias-space
+
+
+# starts multiple args as programs in background
+background() {
+ for ((i=2;i<=$#;i++)); do
+ ${@[1]} ${@[$i]} &> /dev/null &
+ done
+}
+
+
+# A function for expanding any aliases before accepting the line as is and executing the entered command
+expand-alias-and-accept-line() {
+ expand-alias-space
+ # zle .backward-delete-char
+ zle .accept-line
+}
+# zle -N accept-line expand-alias-and-accept-line
+
+
+bindkey '^ ' expand-alias-space # ctrl-space to bypass completion
+bindkey ' ' magic-space
+bindkey -M isearch ' ' magic-space
+
+
+# file completion patterns
+zstyle ':completion:*:*:nvim:*' file-patterns '^*.(pdf|odt|ods|doc|docx|xls|xlsx|odp|ppt|pptx|mp4|mkv|aux):source-files' '*:all-files'
+zstyle ':completion:*:*:(build-workshop|build-document):*' file-patterns '*.mom'
diff --git a/ar/.config/zsh/git.zsh b/ar/.config/zsh/git.zsh
new file mode 100644
index 0000000..e5633bf
--- /dev/null
+++ b/ar/.config/zsh/git.zsh
@@ -0,0 +1,7 @@
+#!/bin/zsh
+
+# Speed up git completion
+# http://talkings.org/post/5236392664/zsh-and-slow-git-completion
+__git_files () {
+ _wanted files expl 'local files' _files
+}
diff --git a/ar/.config/zsh/keymaps.zsh b/ar/.config/zsh/keymaps.zsh
new file mode 100644
index 0000000..b2195d4
--- /dev/null
+++ b/ar/.config/zsh/keymaps.zsh
@@ -0,0 +1,342 @@
+#!/bin/zsh
+
+### --- CUSTOM FUNCTIONS --- ###
+# man
+function man-command-line() { pre_cmd "man"; }
+
+# sudo
+function sudo-command-line() { pre_cmd "sudo"; }
+
+# clears the shell and displays the dir tree with level 2
+function clear-tree-2() {
+ clear
+ tree -L 2
+ zle reset-prompt
+}
+zle -N clear-tree-2
+
+# clears the shell and displays the dir tree with level 3
+function clear-tree-3() { clear && tree -L 3 && zle reset-prompt; }
+zle -N clear-tree-3
+
+# prints the current date in ISO 8601
+function print-current-date() { LBUFFER+=$(date -I); }
+zle -N print-current-date
+
+# prints the current Unix timestamp
+function print-unix-timestamp() { LBUFFER+=$(date +%s); }
+zle -N print-unix-timestamp
+
+# git status
+function git-status() { clear && git status && zle reset-prompt; }
+zle -N git-status
+
+# appends the clipboard contents to the buffer
+function vi-append-clip-selection() { char=${RBUFFER:0:1} && RBUFFER=${RBUFFER:1} && RBUFFER=$char$(clippaste)$RBUFFER; }
+
+# copy
+function detect-clipboard() {
+ emulate -L zsh
+
+ if [[ "${OSTYPE}" == darwin* ]] && (( ${+commands[pbcopy]} )) && (( ${+commands[pbpaste]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | pbcopy; }
+ function clippaste() { pbpaste; }
+ elif [[ "${OSTYPE}" == (cygwin|msys)* ]]; then
+ function clipcopy() { cat "${1:-/dev/stdin}" > /dev/clipboard; }
+ function clippaste() { cat /dev/clipboard; }
+ elif (( $+commands[clip.exe] )) && (( $+commands[powershell.exe] )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | clip.exe; }
+ function clippaste() { powershell.exe -noprofile -command Get-Clipboard; }
+ elif [ -n "${WAYLAND_DISPLAY:-}" ] && (( ${+commands[wl-copy]} )) && (( ${+commands[wl-paste]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | wl-copy &>/dev/null &|; }
+ function clippaste() { wl-paste --no-newline; }
+ elif [ -n "${DISPLAY:-}" ] && (( ${+commands[xsel]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | xsel --clipboard --input; }
+ function clippaste() { xsel --clipboard --output; }
+ elif [ -n "${DISPLAY:-}" ] && (( ${+commands[xclip]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | xclip -selection clipboard -in &>/dev/null &|; }
+ function clippaste() { xclip -out -selection clipboard; }
+ elif (( ${+commands[lemonade]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | lemonade copy; }
+ function clippaste() { lemonade paste; }
+ elif (( ${+commands[doitclient]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | doitclient wclip; }
+ function clippaste() { doitclient wclip -r; }
+ elif (( ${+commands[win32yank]} )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | win32yank -i; }
+ function clippaste() { win32yank -o; }
+ elif [[ $OSTYPE == linux-android* ]] && (( $+commands[termux-clipboard-set] )); then
+ function clipcopy() { cat "${1:-/dev/stdin}" | termux-clipboard-set; }
+ function clippaste() { termux-clipboard-get; }
+ elif [ -n "${TMUX:-}" ] && (( ${+commands[tmux]} )); then
+ function clipcopy() { tmux load-buffer "${1:--}"; }
+ function clippaste() { tmux save-buffer -; }
+ else
+ function _retry_clipboard_detection_or_fail() {
+ local clipcmd="${1}"; shift
+ if detect-clipboard; then
+ "${clipcmd}" "$@"
+ else
+ print "${clipcmd}: Platform $OSTYPE not supported or xclip/xsel not installed" >&2
+ return 1
+ fi
+ }
+ function clipcopy() { _retry_clipboard_detection_or_fail clipcopy "$@"; }
+ function clippaste() { _retry_clipboard_detection_or_fail clippaste "$@"; }
+ return 1
+ fi
+}
+
+function clipcopy clippaste {
+ unfunction clipcopy clippaste
+ detect-clipboard || true # let one retry
+ "$0" "$@"
+}
+
+function copybuffer () {
+ if builtin which clipcopy &>/dev/null; then
+ printf "%s" "$BUFFER" | clipcopy
+ fi
+}
+
+# Function to switch to the left tmux pane and maximize it
+function tmux_left_pane() {
+ export TMUX_PANE_DIRECTION="right"
+ if [[ $TMUX_PANE_DIRECTION == "right" ]]; then
+ tmux select-pane -L # Move to the left (opposite of right)
+ elif [[ $TMUX_PANE_DIRECTION == "bottom" ]]; then
+ tmux select-pane -U # Move to the top (opposite of bottom)
+ fi
+ tmux resize-pane -Z
+}
+
+
+### --- GLOBAL --- ###
+# emacs style
+bindkey '^a' beginning-of-line
+bindkey '^e' end-of-line
+
+# function key bindings
+bindkey '^X^H' clear-tree-2
+bindkey '^X^J' clear-tree-3
+bindkey '^X^S' git-status
+bindkey '^X^X^T' print-current-date
+bindkey '^X^X^U' print-unix-timestamp
+
+
+### --- VI-MODE --- ###
+if [[ -f "${ZPLUGINDIR:-${HOME}/.local/bin/zsh}/zsh-vi-mode/zsh-vi-mode.plugin.zsh" ]]; then
+ ### --- ZSH-VI-MODE--- ###
+ # config
+ ZVM_INIT_MODE=sourcing
+ ZVM_VI_ESCAPE_BINDKEY=jk
+ ZVM_VI_INSERT_ESCAPE_BINDKEY=$ZVM_VI_ESCAPE_BINDKEY
+ ZVM_VI_VISUAL_ESCAPE_BINDKEY=$ZVM_VI_ESCAPE_BINDKEY
+ ZVM_VI_OPPEND_ESCAPE_BINDKEY=$ZVM_VI_ESCAPE_BINDKEY
+ ZVM_INSERT_MODE_CURSOR=$ZVM_CURSOR_BLINKING_BEAM
+ ZVM_NORMAL_MODE_CURSOR=$ZVM_CURSOR_BLOCK
+ ZVM_OPPEND_MODE_CURSOR=$ZVM_CURSOR_UNDERLINE
+ ZVM_LAZY_KEYBINDINGS=false
+ # ZVM_VI_HIGHLIGHT_BACKGROUND=#458588
+
+
+ function zvm_bind_script() {
+ local keymap="$1"
+ local key="$2"
+ local script="$3"
+
+ # Dynamically define a widget to run the script
+ eval "function run_script_${keymap}_${key//\^/}() {
+ zle -I
+ $script
+ zle reset-prompt
+ }"
+
+ # Register the widget with zsh-vi-mode
+ zvm_define_widget "run_script_${keymap}_${key//\^/}"
+ zvm_bindkey "$keymap" "$key" "run_script_${keymap}_${key//\^/}"
+ }
+
+ function zvm_after_init() {
+ ### --- KEY BINDINGS --- ###
+ # programs & scripts
+ bindkey -s '^B' '^ubc -lq\n'
+ bindkey -s '^D' '^ucdi\n'
+ bindkey -s '^F' '^ufzffiles\n'
+ bindkey -s '^G' '^ulf\n'
+ bindkey -s '^K' '^uhtop\n'
+ bindkey -s '^N' '^ulastnvim\n'
+ bindkey -s '^O' '^utmo\n'
+ bindkey -s '^P' '^ufzfpass\n'
+ bindkey -s '^T' '^usessionizer\n'
+ bindkey -s '^V' '^uv.\n'
+ bindkey -s '^Y' '^ulfcd\n'
+ bindkey -s '^Z' '^upd\n'
+ # bindkey -s '^_' '^u\n'
+
+ # ctrl+x key bindings
+ zvm_bind_script viins '^X^A' 'ali'
+ zvm_bind_script viins '^X^B' 'gitopenbranch'
+ zvm_bind_script viins '^X^D' 'fD'
+ zvm_bind_script viins '^X^F' 'gitfiles'
+ zvm_bind_script viins '^X^G' 'rgafiles '
+ zvm_bind_script viins '^X^K' 'fpkill'
+ zvm_bind_script viins '^X^L' 'gloac'
+ zvm_bind_script viins '^X^N' 'lastnvim -l'
+ # zvm_bind_script viins '^X^O' '^u\n'
+ zvm_bind_script viins '^X^R' 'fgst'
+ zvm_bind_script viins '^X^T' 'gitstagedfiles'
+ zvm_bind_script viins '^X^U' 'gitupdate'
+ # zvm_bind_script viins '^X^]' '^u\n'
+ zvm_bind_script viins '^X^_' 'fzffns'
+ zvm_bind_script viins '^X^X^B' 'rbackup'
+ zvm_bind_script viins '^X^X^P' 'pcyr'
+ zvm_bind_script viins '^X^X^R' 'rbackup -r'
+ zvm_bind_script viins '^X^X^S' 'sshadd'
+ zvm_bind_script viins '^X^X^Y' 'yay -Syu && remaps'
+
+ # widgets
+ zvm_define_widget sudo-command-line
+ zvm_bindkey vicmd '^S' sudo-command-line
+ zvm_bindkey viins '^S' sudo-command-line
+ zvm_define_widget insert_last_command_output
+ zvm_bindkey viins '^]' insert_last_command_output
+ zvm_define_widget tmux_left_pane
+ zvm_bindkey vicmd '^[\' tmux_left_pane
+ zvm_bindkey viins '^[\' tmux_left_pane
+ zvm_define_widget man-command-line
+ zvm_bindkey vicmd '^X^M' man-command-line
+ zvm_bindkey viins '^X^M' man-command-line
+ zvm_define_widget vi-append-clip-selection
+ zvm_bindkey viins "^X^P" vi-append-clip-selection
+ zvm_bindkey vicmd "^X^P" vi-append-clip-selection
+ zvm_define_widget copybuffer
+ zvm_bindkey viins "^X^Y" copybuffer
+ zvm_bindkey vicmd "^X^Y" copybuffer
+ }
+
+ # key bindings (lazy)
+ # function zvm_after_lazy_keybindings() {
+ #
+ # }
+
+ # Append a command directly
+ # Since the default initialization mode, this plugin will overwrite the previous key
+ # bindings, this causes the key bindings of other plugins (i.e. fzf, zsh-autocomplete, etc.) to fail.
+ # zvm_after_init_commands+=('[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh')
+ # function zvm_after_init() {
+ # [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
+ # }
+else
+ ### --- Built-in --- ###
+ # Cursor shape
+ bindkey -v # activate vim mode.
+ KEYTIMEOUT=5
+
+ # Change cursor shape for different vi modes.
+ function zle-keymap-select () {
+ case "$KEYMAP $1" in
+ vicmd*|*block) echo -ne '\e[1 q' ;; # block
+ viins*|main*|''|*beam) echo -ne '\e[5 q' ;; # beam
+ esac
+ }
+ zle -N zle-keymap-select
+
+ function zle-line-init() {
+ zle -K viins # initiate `vi insert` as keymap (can be removed if `bindkey -V` has been set elsewhere)
+ echo -ne "\e[5 q"
+ }
+ zle -N zle-line-init
+ echo -ne '\e[5 q' # Use beam shape cursor on startup.
+ function preexec() { echo -ne '\e[5 q' ;} # Use beam shape cursor for each new prompt.
+
+
+ ### --- VI-MODE KEY BINDINGS --- ###
+ bindkey -M menuselect 'h' vi-backward-char
+ bindkey -M menuselect 'l' vi-forward-char
+ bindkey -M menuselect 'k' vi-up-line-or-history
+ bindkey -M menuselect 'j' vi-down-line-or-history
+ bindkey -v '^?' backward-delete-char
+ bindkey '^[[P' delete-char
+
+ # edit line in vim with ctrl-v in viins and ctrl-e in vicmd
+ autoload edit-command-line
+ zle -N edit-command-line
+ bindkey '^x^v' edit-command-line # ctrl-v
+ bindkey -M vicmd '^[[P' vi-delete-char # delete
+ bindkey -M vicmd '^e' edit-command-line # ctrl-e
+ bindkey -M visual '^[[P' vi-delete # delete
+ bindkey -M viins 'jk' vi-cmd-mode # normal mode
+
+ # last command output
+ zle -N insert_last_command_output
+ bindkey -M viins '^]' insert_last_command_output
+
+ # man
+ zle -N man-command-line
+ bindkey -M emacs '^X^M' man-command-line
+ bindkey -M vicmd '^X^M' man-command-line
+ bindkey -M viins '^X^M' man-command-line
+
+ # sudo
+ zle -N sudo-command-line
+ bindkey -M emacs '^S' sudo-command-line
+ bindkey -M vicmd '^S' sudo-command-line
+ bindkey -M viins '^S' sudo-command-line
+
+ # bind y/Y to yank until end of line/yank whole line
+ # bindkey -M vicmd y zsh-system-clipboard-vicmd-vi-yank-eol
+ # bindkey -M vicmd Y zsh-system-clipboard-vicmd-vi-yank-whole-line
+
+ # appends the clipboard contents to the buffer
+ zle -N vi-append-clip-selection
+ bindkey -M emacs "^X^P" vi-append-clip-selection
+ bindkey -M viins "^X^P" vi-append-clip-selection
+ bindkey -M vicmd "^X^P" vi-append-clip-selection
+
+ # copy buffer
+ zle -N copybuffer
+ bindkey -M emacs "^X^Y" copybuffer
+ bindkey -M viins "^X^Y" copybuffer
+ bindkey -M vicmd "^X^Y" copybuffer
+
+ # Register the function as a ZLE widget
+ zle -N tmux_left_pane
+ bindkey -M vicmd '^[\' tmux_left_pane
+ bindkey -M viins '^[\' tmux_left_pane
+
+ ### --- DEFAULT KEY BINDINGS --- ###
+ # programs & scripts
+ bindkey -s '^B' '^ubc -lq\n'
+ bindkey -s '^D' '^ucdi\n'
+ bindkey -s '^F' '^ufzffiles\n'
+ bindkey -s '^G' '^ulf\n'
+ bindkey -s '^K' '^uhtop\n'
+ bindkey -s '^N' '^ulastnvim\n'
+ bindkey -s '^O' '^utmo\n'
+ bindkey -s '^P' '^ufzfpass\n'
+ bindkey -s '^T' '^usessionizer\n'
+ bindkey -s '^V' '^uv.\n'
+ bindkey -s '^Y' '^ulfcd\n'
+ bindkey -s '^Z' '^upd\n'
+ # bindkey -s '^_' '^u\n'
+ bindkey -s '^X^A' '^uali\n'
+ bindkey -s '^X^B' '^ugitopenbranch\n'
+ bindkey -s '^X^D' '^ufD\n'
+ bindkey -s '^X^F' '^ugitfiles\n'
+ bindkey -s '^X^G' '^urgafiles '
+ bindkey -s '^X^K' '^ufpkill\n'
+ bindkey -s '^X^L' '^ugloac\n'
+ bindkey -s '^X^N' '^ulastnvim -l\n'
+ # bindkey -s '^X^O' '^u\n'
+ bindkey -s '^X^R' '^ufgst\n'
+ bindkey -s '^X^T' '^ugitstagedfiles\n'
+ bindkey -s '^X^U' '^ugitupdate\n'
+ # bindkey -s '^X^]' '^u\n'
+ bindkey -s '^X^_' '^ufzffns\n'
+ bindkey -s '^X^X^B' '^urbackup\n'
+ bindkey -s '^X^X^P' '^upcyr\n'
+ bindkey -s '^X^X^R' '^urbackup -r\n'
+ bindkey -s '^X^X^S' '^usshadd\n'
+ bindkey -s '^X^X^Y' '^uyay -Syu && remaps\n'
+fi
diff --git a/ar/.config/zsh/p10k.zsh b/ar/.config/zsh/p10k.zsh
new file mode 100644
index 0000000..604d76d
--- /dev/null
+++ b/ar/.config/zsh/p10k.zsh
@@ -0,0 +1,1721 @@
+#!/bin/zsh
+
+# Generated by Powerlevel10k configuration wizard on 2024-03-06 at 08:21 CST.
+# Based on romkatv/powerlevel10k/config/p10k-lean.zsh, checksum 52020.
+# Wizard options: nerdfont-v3 + powerline, large icons, unicode, lean, 24h time,
+# 2 lines, disconnected, no frame, sparse, many icons, fluent, transient_prompt,
+# instant_prompt=verbose.
+# Type `p10k configure` to generate another config.
+#
+# Config for Powerlevel10k with lean prompt style. Type `p10k configure` to generate
+# your own config based on it.
+#
+# Tip: Looking for a nice color? Here's a one-liner to print colormap.
+#
+# for i in {0..255}; do print -Pn "%K{$i} %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done
+
+# Temporarily change options.
+'builtin' 'local' '-a' 'p10k_config_opts'
+[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
+[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
+[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
+'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
+
+() {
+ emulate -L zsh -o extended_glob
+
+ # Unset all configuration options. This allows you to apply configuration changes without
+ # restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
+
+ # Zsh >= 5.1 is required.
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
+
+ # The list of segments shown on the left. Fill it with the most important segments.
+ typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
+ # =========================[ Line #1 ]=========================
+ os_icon # os identifier
+ dir # current directory
+ vcs # git status
+ # =========================[ Line #2 ]=========================
+ newline # \n
+ prompt_char # prompt symbol
+ )
+
+ # The list of segments shown on the right. Fill it with less important segments.
+ # Right prompt on the last prompt line (where you are typing your commands) gets
+ # automatically hidden when the input line reaches it. Right prompt above the
+ # last prompt line gets hidden if it would overlap with left prompt.
+ typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
+ # =========================[ Line #1 ]=========================
+ status # exit code of the last command
+ command_execution_time # duration of the last command
+ background_jobs # presence of background jobs
+ direnv # direnv status (https://direnv.net/)
+ asdf # asdf version manager (https://github.com/asdf-vm/asdf)
+ virtualenv # python virtual environment (https://docs.python.org/3/library/venv.html)
+ anaconda # conda environment (https://conda.io/)
+ pyenv # python environment (https://github.com/pyenv/pyenv)
+ goenv # go environment (https://github.com/syndbg/goenv)
+ nodenv # node.js version from nodenv (https://github.com/nodenv/nodenv)
+ nvm # node.js version from nvm (https://github.com/nvm-sh/nvm)
+ nodeenv # node.js environment (https://github.com/ekalinin/nodeenv)
+ # node_version # node.js version
+ # go_version # go version (https://golang.org)
+ # rust_version # rustc version (https://www.rust-lang.org)
+ # dotnet_version # .NET version (https://dotnet.microsoft.com)
+ # php_version # php version (https://www.php.net/)
+ # laravel_version # laravel php framework version (https://laravel.com/)
+ # java_version # java version (https://www.java.com/)
+ # package # name@version from package.json (https://docs.npmjs.com/files/package.json)
+ rbenv # ruby version from rbenv (https://github.com/rbenv/rbenv)
+ rvm # ruby version from rvm (https://rvm.io)
+ fvm # flutter version management (https://github.com/leoafarias/fvm)
+ luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
+ jenv # java version from jenv (https://github.com/jenv/jenv)
+ plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
+ perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew)
+ phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
+ scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
+ haskell_stack # haskell version from stack (https://haskellstack.org/)
+ kubecontext # current kubernetes context (https://kubernetes.io/)
+ terraform # terraform workspace (https://www.terraform.io)
+ # terraform_version # terraform version (https://www.terraform.io)
+ aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
+ aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
+ azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
+ gcloud # google cloud cli account and project (https://cloud.google.com/)
+ google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
+ toolbox # toolbox name (https://github.com/containers/toolbox)
+ context # user@hostname
+ nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
+ ranger # ranger shell (https://github.com/ranger/ranger)
+ yazi # yazi shell (https://github.com/sxyazi/yazi)
+ nnn # nnn shell (https://github.com/jarun/nnn)
+ lf # lf shell (https://github.com/gokcehan/lf)
+ xplr # xplr shell (https://github.com/sayanarijit/xplr)
+ vim_shell # vim shell indicator (:sh)
+ midnight_commander # midnight commander shell (https://midnight-commander.org/)
+ nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
+ chezmoi_shell # chezmoi shell (https://www.chezmoi.io/)
+ # vpn_ip # virtual private network indicator
+ load # CPU load
+ # disk_usage # disk usage
+ ram # free RAM
+ # swap # used swap
+ todo # todo items (https://github.com/todotxt/todo.txt-cli)
+ timewarrior # timewarrior tracking status (https://timewarrior.net/)
+ taskwarrior # taskwarrior task count (https://taskwarrior.org/)
+ per_directory_history # Oh My Zsh per-directory-history local/global indicator
+ # cpu_arch # CPU architecture
+ time # current time
+ # =========================[ Line #2 ]=========================
+ newline
+ # ip # ip address and bandwidth usage for a specified network interface
+ # public_ip # public IP address
+ proxy # system-wide http/https/ftp proxy
+ battery # internal battery
+ # wifi # wifi speed
+ # example # example user-defined segment (see prompt_example function below)
+ )
+
+ # Defines character set used by powerlevel10k. It's best to let `p10k configure` set it for you.
+ typeset -g POWERLEVEL9K_MODE=nerdfont-v3
+ # When set to `moderate`, some icons will have an extra space after them. This is meant to avoid
+ # icon overlap when using non-monospace fonts. When set to `none`, spaces are not added.
+ typeset -g POWERLEVEL9K_ICON_PADDING=moderate
+
+ # Basic style options that define the overall look of your prompt. You probably don't want to
+ # change them.
+ typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
+ typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
+ typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
+ typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
+
+ # When set to true, icons appear before content on both sides of the prompt. When set
+ # to false, icons go after content. If empty or not set, icons go before content in the left
+ # prompt and after content in the right prompt.
+ #
+ # You can also override it for a specific segment:
+ #
+ # POWERLEVEL9K_STATUS_ICON_BEFORE_CONTENT=false
+ #
+ # Or for a specific segment in specific state:
+ #
+ # POWERLEVEL9K_DIR_NOT_WRITABLE_ICON_BEFORE_CONTENT=false
+ typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=true
+
+ # Add an empty line before each prompt.
+ typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true
+
+ # Connect left prompt lines with these symbols.
+ typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX=
+ typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX=
+ typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX=
+ # Connect right prompt lines with these symbols.
+ typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=
+ typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=
+ typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=
+
+ # The left end of left prompt.
+ typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=
+ # The right end of right prompt.
+ typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL=
+
+ # Ruler, a.k.a. the horizontal line before each prompt. If you set it to true, you'll
+ # probably want to set POWERLEVEL9K_PROMPT_ADD_NEWLINE=false above and
+ # POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' below.
+ typeset -g POWERLEVEL9K_SHOW_RULER=false
+ typeset -g POWERLEVEL9K_RULER_CHAR='─' # reasonable alternative: '·'
+ typeset -g POWERLEVEL9K_RULER_FOREGROUND=242
+
+ # Filler between left and right prompt on the first prompt line. You can set it to '·' or '─'
+ # to make it easier to see the alignment between left and right prompt and to separate prompt
+ # from command output. It serves the same purpose as ruler (see above) without increasing
+ # the number of prompt lines. You'll probably want to set POWERLEVEL9K_SHOW_RULER=false
+ # if using this. You might also like POWERLEVEL9K_PROMPT_ADD_NEWLINE=false for more compact
+ # prompt.
+ typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '
+ if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then
+ # The color of the filler.
+ typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=242
+ # Add a space between the end of left prompt and the filler.
+ typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=' '
+ # Add a space between the filler and the start of right prompt.
+ typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL=' '
+ # Start filler from the edge of the screen if there are no left segments on the first line.
+ typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}'
+ # End filler on the edge of the screen if there are no right segments on the first line.
+ typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}'
+ fi
+
+ #################################[ os_icon: os identifier ]##################################
+ # OS identifier color.
+ typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='⭐'
+
+ ################################[ prompt_char: prompt symbol ]################################
+ # Green prompt symbol if the last command succeeded.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76
+ # Red prompt symbol if the last command failed.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196
+ # Default prompt symbol.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯'
+ # Prompt symbol in command vi mode.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION=''
+ # Prompt symbol in visual vi mode.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
+ # Prompt symbol in overwrite vi mode.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶'
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
+ # No line terminator if prompt_char is the last segment.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=''
+ # No line introducer if prompt_char is the first segment.
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=
+
+ ##################################[ dir: current directory ]##################################
+ # Default current directory color.
+ typeset -g POWERLEVEL9K_DIR_FOREGROUND=31
+ # If directory is too long, shorten some of its segments to the shortest possible unique
+ # prefix. The shortened directory can be tab-completed to the original.
+ typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique
+ # Replace removed segment suffixes with this symbol.
+ typeset -g POWERLEVEL9K_SHORTEN_DELIMITER=
+ # Color of the shortened directory segments.
+ typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103
+ # Color of the anchor directory segments. Anchor segments are never shortened. The first
+ # segment is always an anchor.
+ typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39
+ # Display anchor directory segments in bold.
+ typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true
+ # Don't shorten directories that contain any of these files. They are anchors.
+ local anchor_files=(
+ .bzr
+ .citc
+ .git
+ .hg
+ .node-version
+ .python-version
+ .go-version
+ .ruby-version
+ .lua-version
+ .java-version
+ .perl-version
+ .php-version
+ .tool-versions
+ .shorten_folder_marker
+ .svn
+ .terraform
+ CVS
+ Cargo.toml
+ composer.json
+ go.mod
+ package.json
+ stack.yaml
+ )
+ typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
+ # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
+ # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
+ # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
+ # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
+ # and other directories don't.
+ #
+ # Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer.
+ # This moves the truncation point to the right (positive offset) or to the left (negative offset)
+ # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
+ # respectively.
+ typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
+ # Don't shorten this many last directory segments. They are anchors.
+ typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
+ # Shorten directory if it's longer than this even if there is space for it. The value can
+ # be either absolute (e.g., '80') or a percentage of terminal width (e.g, '50%'). If empty,
+ # directory will be shortened only when prompt doesn't fit or when other parameters demand it
+ # (see POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS and POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT below).
+ # If set to `0`, directory will always be shortened to its minimum length.
+ typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80
+ # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least this
+ # many columns for typing commands.
+ typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40
+ # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least
+ # COLUMNS * POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT * 0.01 columns for typing commands.
+ typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50
+ # If set to true, embed a hyperlink into the directory. Useful for quickly
+ # opening a directory in the file manager simply by clicking the link.
+ # Can also be handy when the directory is shortened, as it allows you to see
+ # the full directory that was used in previous commands.
+ typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
+
+ # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
+ # and POWERLEVEL9K_DIR_CLASSES below.
+ typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
+
+ # The default icon shown next to non-writable and non-existent directories when
+ # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
+ # typeset -g POWERLEVEL9K_LOCK_ICON='⭐'
+
+ # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
+ # directories. It must be an array with 3 * N elements. Each triplet consists of:
+ #
+ # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
+ # extended_glob option enabled.
+ # 2. Directory class for the purpose of styling.
+ # 3. An empty string.
+ #
+ # Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
+ #
+ # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
+ # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_DIR_CLASSES=(
+ # '~/work(|/*)' WORK ''
+ # '~(|/*)' HOME ''
+ # '*' DEFAULT '')
+ #
+ # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
+ # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
+ # WORK_NON_EXISTENT.
+ #
+ # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
+ # option to define custom colors and icons for different directory classes.
+ #
+ # # Styling for WORK.
+ # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
+ #
+ # # Styling for WORK_NOT_WRITABLE.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39
+ #
+ # # Styling for WORK_NON_EXISTENT.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39
+ #
+ # If a styling parameter isn't explicitly defined for some class, it falls back to the classless
+ # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
+ # back to POWERLEVEL9K_DIR_FOREGROUND.
+ #
+ # typeset -g POWERLEVEL9K_DIR_CLASSES=()
+
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
+
+ #####################################[ vcs: git status ]######################################
+ # Branch icon. Set this parameter to '\UE0A0 ' for the popular Powerline branch icon.
+ typeset -g POWERLEVEL9K_VCS_BRANCH_ICON='\uF126 '
+
+ # Untracked files icon. It's really a question mark, your font isn't broken.
+ # Change the value of this parameter to show a different icon.
+ typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?'
+
+ # Formatter for Git status.
+ #
+ # Example output: master wip ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
+ #
+ # You can edit the function to customize how Git status looks.
+ #
+ # VCS_STATUS_* parameters are set by gitstatus plugin. See reference:
+ # https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh.
+ function my_git_formatter() {
+ emulate -L zsh
+
+ if [[ -n $P9K_CONTENT ]]; then
+ # If P9K_CONTENT is not empty, use it. It's either "loading" or from vcs_info (not from
+ # gitstatus plugin). VCS_STATUS_* parameters are not available in this case.
+ typeset -g my_git_format=$P9K_CONTENT
+ return
+ fi
+
+ if (( $1 )); then
+ # Styling for up-to-date Git status.
+ local meta='%f' # default foreground
+ local clean='%76F' # green foreground
+ local modified='%178F' # yellow foreground
+ local untracked='%39F' # blue foreground
+ local conflicted='%196F' # red foreground
+ else
+ # Styling for incomplete and stale Git status.
+ local meta='%244F' # grey foreground
+ local clean='%244F' # grey foreground
+ local modified='%244F' # grey foreground
+ local untracked='%244F' # grey foreground
+ local conflicted='%244F' # grey foreground
+ fi
+
+ local res
+
+ if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
+ local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
+ # If local branch name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 … the last 12.
+ # Tip: To always show local branch name in full without truncation, delete the next line.
+ (( $#branch > 32 )) && branch[13,-13]="…" # <-- this line
+ res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
+ fi
+
+ if [[ -n $VCS_STATUS_TAG
+ # Show tag only if not on a branch.
+ # Tip: To always show tag, delete the next line.
+ && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
+ ]]; then
+ local tag=${(V)VCS_STATUS_TAG}
+ # If tag name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 … the last 12.
+ # Tip: To always show tag name in full without truncation, delete the next line.
+ (( $#tag > 32 )) && tag[13,-13]="…" # <-- this line
+ res+="${meta}#${clean}${tag//\%/%%}"
+ fi
+
+ # Display the current Git commit if there is no branch and no tag.
+ # Tip: To always display the current Git commit, delete the next line.
+ [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
+ res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
+
+ # Show tracking branch name if it differs from local branch.
+ if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
+ res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
+ fi
+
+ # Display "wip" if the latest commit's summary contains "wip" or "WIP".
+ if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
+ res+=" ${modified}wip"
+ fi
+
+ if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
+ # ⇣42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}"
+ # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}"
+ elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
+ # Tip: Uncomment the next line to display '=' if up to date with the remote.
+ # res+=" ${clean}="
+ fi
+
+ # ⇠42 if behind the push remote.
+ (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}"
+ (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
+ # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
+ (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}"
+ # *42 if have stashes.
+ (( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}"
+ # 'merge' if the repo is in an unusual state.
+ [[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}"
+ # ~42 if have merge conflicts.
+ (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
+ # +42 if have staged changes.
+ (( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
+ # !42 if have unstaged changes.
+ (( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
+ # ?42 if have untracked files. It's really a question mark, your font isn't broken.
+ # See POWERLEVEL9K_VCS_UNTRACKED_ICON above if you want to use a different icon.
+ # Remove the next line if you don't want to see untracked files at all.
+ (( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}"
+ # "─" if the number of unstaged files is unknown. This can happen due to
+ # POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY (see below) being set to a non-negative number lower
+ # than the number of files in the Git index, or due to bash.showDirtyState being set to false
+ # in the repository config. The number of staged and untracked files may also be unknown
+ # in this case.
+ (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─"
+
+ typeset -g my_git_format=$res
+ }
+ functions -M my_git_formatter 2>/dev/null
+
+ # Don't count the number of unstaged, untracked and conflicted files in Git repositories with
+ # more than this many files in the index. Negative value means infinity.
+ #
+ # If you are working in Git repositories with tens of millions of files and seeing performance
+ # sagging, try setting POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY to a number lower than the output
+ # of `git ls-files | wc -l`. Alternatively, add `bash.showDirtyState = false` to the repository's
+ # config: `git config bash.showDirtyState false`.
+ typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1
+
+ # Don't show Git status in prompt for repositories whose workdir matches this pattern.
+ # For example, if set to '~', the Git repository at $HOME/.git will be ignored.
+ # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
+ typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
+
+ # Disable the default Git status formatting.
+ typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
+ # Install our own Git status formatter.
+ typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}'
+ typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}'
+ # Enable counters for staged, unstaged, etc.
+ typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1
+
+ # Icon color.
+ typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76
+ typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # Custom prefix.
+ typeset -g POWERLEVEL9K_VCS_PREFIX='%fon '
+
+ # Show status of repositories of these types. You can add svn and/or hg if you are
+ # using them. If you do, your prompt may become slow even when your current directory
+ # isn't in an svn or hg repository.
+ typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
+
+ # These settings are used for repositories other than Git or when gitstatusd fails and
+ # Powerlevel10k has to fall back to using vcs_info.
+ typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
+ typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76
+ typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178
+
+ ##########################[ status: exit code of the last command ]###########################
+ # Enable OK_PIPE, ERROR_PIPE and ERROR_SIGNAL status states to allow us to enable, disable and
+ # style them independently from the regular OK and ERROR state.
+ typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true
+
+ # Status on success. No content, just an icon. No need to show it if prompt_char is enabled as
+ # it will signify success by turning green.
+ typeset -g POWERLEVEL9K_STATUS_OK=false
+ typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=70
+ typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔'
+
+ # Status when some part of a pipe command fails but the overall exit status is zero. It may look
+ # like this: 1|0.
+ typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true
+ typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=70
+ typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔'
+
+ # Status when it's just an error code (e.g., '1'). No need to show it if prompt_char is enabled as
+ # it will signify error by turning red.
+ typeset -g POWERLEVEL9K_STATUS_ERROR=false
+ typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=160
+ typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘'
+
+ # Status when the last command was terminated by a signal.
+ typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true
+ typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=160
+ # Use terse signal names: "INT" instead of "SIGINT(2)".
+ typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false
+ typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘'
+
+ # Status when some part of a pipe command fails and the overall exit status is also non-zero.
+ # It may look like this: 1|0.
+ typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true
+ typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=160
+ typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
+
+ ###################[ command_execution_time: duration of the last command ]###################
+ # Show duration of the last command if takes at least this many seconds.
+ typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
+ # Show this many fractional digits. Zero means round to seconds.
+ typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
+ # Execution time color.
+ typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=101
+ # Duration format: 1d 2h 3m 4s.
+ typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # Custom prefix.
+ typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PREFIX='%ftook '
+
+ #######################[ background_jobs: presence of background jobs ]#######################
+ # Don't show the number of background jobs.
+ typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false
+ # Background jobs color.
+ typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=70
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #######################[ direnv: direnv status (https://direnv.net/) ]########################
+ # Direnv color.
+ typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=178
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_DIRENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
+ # Default asdf color. Only used to display tools for which there is no color override (see below).
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
+ typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66
+
+ # There are four parameters that can be used to hide asdf tools. Each parameter describes
+ # conditions under which a tool gets hidden. Parameters can hide tools but not unhide them. If at
+ # least one parameter decides to hide a tool, that tool gets hidden. If no parameter decides to
+ # hide a tool, it gets shown.
+ #
+ # Special note on the difference between POWERLEVEL9K_ASDF_SOURCES and
+ # POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW. Consider the effect of the following commands:
+ #
+ # asdf local python 3.8.1
+ # asdf global python 3.8.1
+ #
+ # After running both commands the current python version is 3.8.1 and its source is "local" as
+ # it takes precedence over "global". If POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW is set to false,
+ # it'll hide python version in this case because 3.8.1 is the same as the global version.
+ # POWERLEVEL9K_ASDF_SOURCES will hide python version only if the value of this parameter doesn't
+ # contain "local".
+
+ # Hide tool versions that don't come from one of these sources.
+ #
+ # Available sources:
+ #
+ # - shell `asdf current` says "set by ASDF_${TOOL}_VERSION environment variable"
+ # - local `asdf current` says "set by /some/not/home/directory/file"
+ # - global `asdf current` says "set by /home/username/file"
+ #
+ # Note: If this parameter is set to (shell local global), it won't hide tools.
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SOURCES.
+ typeset -g POWERLEVEL9K_ASDF_SOURCES=(shell local global)
+
+ # If set to false, hide tool versions that are the same as global.
+ #
+ # Note: The name of this parameter doesn't reflect its meaning at all.
+ # Note: If this parameter is set to true, it won't hide tools.
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_PROMPT_ALWAYS_SHOW.
+ typeset -g POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW=false
+
+ # If set to false, hide tool versions that are equal to "system".
+ #
+ # Note: If this parameter is set to true, it won't hide tools.
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_SYSTEM.
+ typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
+
+ # If set to non-empty value, hide tools unless there is a file matching the specified file pattern
+ # in the current directory, or its parent directory, or its grandparent directory, and so on.
+ #
+ # Note: If this parameter is set to empty value, it won't hide tools.
+ # Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_ON_UPGLOB.
+ #
+ # Example: Hide nodejs version when there is no package.json and no *.js files in the current
+ # directory, in `..`, in `../..` and so on.
+ #
+ # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.js|package.json'
+ typeset -g POWERLEVEL9K_ASDF_SHOW_ON_UPGLOB=
+
+ # Ruby version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_RUBY_FOREGROUND=168
+ # typeset -g POWERLEVEL9K_ASDF_RUBY_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_RUBY_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Python version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_PYTHON_FOREGROUND=37
+ # typeset -g POWERLEVEL9K_ASDF_PYTHON_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_PYTHON_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Go version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_GOLANG_FOREGROUND=37
+ # typeset -g POWERLEVEL9K_ASDF_GOLANG_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_GOLANG_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Node.js version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_NODEJS_FOREGROUND=70
+ # typeset -g POWERLEVEL9K_ASDF_NODEJS_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Rust version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_RUST_FOREGROUND=37
+ # typeset -g POWERLEVEL9K_ASDF_RUST_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_RUST_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # .NET Core version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_FOREGROUND=134
+ # typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_DOTNET_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Flutter version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_FLUTTER_FOREGROUND=38
+ # typeset -g POWERLEVEL9K_ASDF_FLUTTER_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_FLUTTER_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Lua version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_LUA_FOREGROUND=32
+ # typeset -g POWERLEVEL9K_ASDF_LUA_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_LUA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Java version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_JAVA_FOREGROUND=32
+ # typeset -g POWERLEVEL9K_ASDF_JAVA_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_JAVA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Perl version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_PERL_FOREGROUND=67
+ # typeset -g POWERLEVEL9K_ASDF_PERL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_PERL_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Erlang version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_ERLANG_FOREGROUND=125
+ # typeset -g POWERLEVEL9K_ASDF_ERLANG_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_ERLANG_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Elixir version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_ELIXIR_FOREGROUND=129
+ # typeset -g POWERLEVEL9K_ASDF_ELIXIR_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_ELIXIR_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Postgres version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_POSTGRES_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_ASDF_POSTGRES_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_POSTGRES_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # PHP version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_PHP_FOREGROUND=99
+ # typeset -g POWERLEVEL9K_ASDF_PHP_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_PHP_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Haskell version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_HASKELL_FOREGROUND=172
+ # typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ # Julia version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
+ ##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
+ # NordVPN connection indicator color.
+ typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39
+ # Hide NordVPN connection indicator when not connected.
+ typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION=
+ typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION=
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NORDVPN_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #################[ ranger: ranger shell (https://github.com/ranger/ranger) ]##################
+ # Ranger shell color.
+ typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ####################[ yazi: yazi shell (https://github.com/sxyazi/yazi) ]#####################
+ # Yazi shell color.
+ typeset -g POWERLEVEL9K_YAZI_FOREGROUND=178
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_YAZI_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]#######################
+ # Nnn shell color.
+ typeset -g POWERLEVEL9K_NNN_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ######################[ lf: lf shell (https://github.com/gokcehan/lf) ]#######################
+ # lf shell color.
+ typeset -g POWERLEVEL9K_LF_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LF_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
+ # xplr shell color.
+ typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###########################[ vim_shell: vim shell indicator (:sh) ]###########################
+ # Vim shell indicator color.
+ typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_VIM_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ######[ midnight_commander: midnight commander shell (https://midnight-commander.org/) ]######
+ # Midnight Commander shell color.
+ typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=178
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #[ nix_shell: nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) ]##
+ # Nix shell color.
+ typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74
+
+ # Display the icon of nix_shell if PATH contains a subdirectory of /nix/store.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_INFER_FROM_PATH=false
+
+ # Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##################[ chezmoi_shell: chezmoi shell (https://www.chezmoi.io/) ]##################
+ # chezmoi shell color.
+ typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=33
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CHEZMOI_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##################################[ disk_usage: disk usage ]##################################
+ # Colors for different levels of disk usage.
+ typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35
+ typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220
+ typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=160
+ # Thresholds for different levels of disk usage (percentage points).
+ typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90
+ typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95
+ # If set to true, hide disk usage when below $POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL percent.
+ typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_DISK_USAGE_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ######################################[ ram: free RAM ]#######################################
+ # RAM color.
+ typeset -g POWERLEVEL9K_RAM_FOREGROUND=66
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_RAM_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #####################################[ swap: used swap ]######################################
+ # Swap color.
+ typeset -g POWERLEVEL9K_SWAP_FOREGROUND=96
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_SWAP_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ######################################[ load: CPU load ]######################################
+ # Show average CPU load over this many last minutes. Valid values are 1, 5 and 15.
+ typeset -g POWERLEVEL9K_LOAD_WHICH=5
+ # Load color when load is under 50%.
+ typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=66
+ # Load color when load is between 50% and 70%.
+ typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=178
+ # Load color when load is over 70%.
+ typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=166
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LOAD_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ################[ todo: todo items (https://github.com/todotxt/todo.txt-cli) ]################
+ # Todo color.
+ typeset -g POWERLEVEL9K_TODO_FOREGROUND=110
+ # Hide todo when the total number of tasks is zero.
+ typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true
+ # Hide todo when the number of tasks after filtering is zero.
+ typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false
+
+ # Todo format. The following parameters are available within the expansion.
+ #
+ # - P9K_TODO_TOTAL_TASK_COUNT The total number of tasks.
+ # - P9K_TODO_FILTERED_TASK_COUNT The number of tasks after filtering.
+ #
+ # These variables correspond to the last line of the output of `todo.sh -p ls`:
+ #
+ # TODO: 24 of 42 tasks shown
+ #
+ # Here 24 is P9K_TODO_FILTERED_TASK_COUNT and 42 is P9K_TODO_TOTAL_TASK_COUNT.
+ #
+ # typeset -g POWERLEVEL9K_TODO_CONTENT_EXPANSION='$P9K_TODO_FILTERED_TASK_COUNT'
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TODO_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###########[ timewarrior: timewarrior tracking status (https://timewarrior.net/) ]############
+ # Timewarrior color.
+ typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=110
+ # If the tracked task is longer than 24 characters, truncate and append "…".
+ # Tip: To always display tasks without truncation, delete the following parameter.
+ # Tip: To hide task names and display just the icon when time tracking is enabled, set the
+ # value of the following parameter to "".
+ typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}'
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TIMEWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
+ # Taskwarrior color.
+ typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74
+
+ # Taskwarrior segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
+ # - P9K_TASKWARRIOR_OVERDUE_COUNT The number of overdue tasks: `task +OVERDUE count`.
+ #
+ # Zero values are represented as empty parameters.
+ #
+ # The default format:
+ #
+ # '${P9K_TASKWARRIOR_OVERDUE_COUNT:+"!$P9K_TASKWARRIOR_OVERDUE_COUNT/"}$P9K_TASKWARRIOR_PENDING_COUNT'
+ #
+ # typeset -g POWERLEVEL9K_TASKWARRIOR_CONTENT_EXPANSION='$P9K_TASKWARRIOR_PENDING_COUNT'
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ######[ per_directory_history: Oh My Zsh per-directory-history local/global indicator ]#######
+ # Color when using local/global history.
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=135
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=130
+
+ # Tip: Uncomment the next two lines to hide "local"/"global" text and leave just the icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_CONTENT_EXPANSION=''
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_CONTENT_EXPANSION=''
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ################################[ cpu_arch: CPU architecture ]################################
+ # CPU architecture color.
+ typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=172
+
+ # Hide the segment when on a specific CPU architecture.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_CONTENT_EXPANSION=
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_VISUAL_IDENTIFIER_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##################################[ context: user@hostname ]##################################
+ # Context color when running with privileges.
+ typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178
+ # Context color in SSH without privileges.
+ typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=180
+ # Default context color (no privileges, no SSH).
+ typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=180
+
+ # Context format when running with privileges: bold user@hostname.
+ typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%B%n@%m'
+ # Context format when in SSH without privileges: user@hostname.
+ typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m'
+ # Default context format (no privileges, no SSH): user@hostname.
+ typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m'
+
+ # Don't show context unless running with privileges or in SSH.
+ # Tip: Remove the next line to always show context.
+ typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CONTEXT_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # Custom prefix.
+ typeset -g POWERLEVEL9K_CONTEXT_PREFIX='%fwith '
+
+ ###[ virtualenv: python virtual environment (https://docs.python.org/3/library/venv.html) ]###
+ # Python virtual environment color.
+ typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37
+ # Don't show Python version next to the virtual environment name.
+ typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
+ # If set to "false", won't show virtualenv if pyenv is already shown.
+ # If set to "if-different", won't show virtualenv if it's the same as pyenv.
+ typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
+ # Separate environment name from Python version only with a space.
+ typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_VIRTUALENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #####################[ anaconda: conda environment (https://conda.io/) ]######################
+ # Anaconda environment color.
+ typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37
+
+ # Anaconda segment format. The following parameters are available within the expansion.
+ #
+ # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
+ # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
+ # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
+ # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
+ #
+ # CONDA_PROMPT_MODIFIER can be configured with the following command:
+ #
+ # conda config --set env_prompt '({default_env}) '
+ #
+ # The last argument is a Python format string that can use the following variables:
+ #
+ # - prefix The same as CONDA_PREFIX.
+ # - default_env The same as CONDA_DEFAULT_ENV.
+ # - name The last segment of CONDA_PREFIX.
+ # - stacked_env Comma-separated list of names in the environment stack. The first element is
+ # always the same as default_env.
+ #
+ # Note: '({default_env}) ' is the default value of env_prompt.
+ #
+ # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
+ # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
+ # is empty.
+ typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ################[ pyenv: python environment (https://github.com/pyenv/pyenv) ]################
+ # Pyenv color.
+ typeset -g POWERLEVEL9K_PYENV_FOREGROUND=37
+ # Hide python version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global)
+ # If set to false, hide python version if it's the same as global:
+ # $(pyenv version-name) == $(pyenv global).
+ typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide python version if it's equal to "system".
+ typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
+
+ # Pyenv segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_CONTENT Current pyenv environment (pyenv version-name).
+ # - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
+ #
+ # The default format has the following logic:
+ #
+ # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
+ # starts with "$P9K_PYENV_PYTHON_VERSION/".
+ # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
+ typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ################[ goenv: go environment (https://github.com/syndbg/goenv) ]################
+ # Goenv color.
+ typeset -g POWERLEVEL9K_GOENV_FOREGROUND=37
+ # Hide go version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global)
+ # If set to false, hide go version if it's the same as global:
+ # $(goenv version-name) == $(goenv global).
+ typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide go version if it's equal to "system".
+ typeset -g POWERLEVEL9K_GOENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_GOENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##########[ nodenv: node.js version from nodenv (https://github.com/nodenv/nodenv) ]##########
+ # Nodenv color.
+ typeset -g POWERLEVEL9K_NODENV_FOREGROUND=70
+ # Hide node version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global)
+ # If set to false, hide node version if it's the same as global:
+ # $(nodenv version-name) == $(nodenv global).
+ typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide node version if it's equal to "system".
+ typeset -g POWERLEVEL9K_NODENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NODENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]###############
+ # Nvm color.
+ typeset -g POWERLEVEL9K_NVM_FOREGROUND=70
+ # If set to false, hide node version if it's the same as default:
+ # $(nvm version current) == $(nvm version default).
+ typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide node version if it's equal to "system".
+ typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ############[ nodeenv: node.js environment (https://github.com/ekalinin/nodeenv) ]############
+ # Nodeenv color.
+ typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=70
+ # Don't show Node version next to the environment name.
+ typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false
+ # Separate environment name from Node version only with a space.
+ typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER=
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NODEENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##############################[ node_version: node.js version ]###############################
+ # Node version color.
+ typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=70
+ # Show node version only when in a directory tree containing package.json.
+ typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_NODE_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #######################[ go_version: go version (https://golang.org) ]########################
+ # Go version color.
+ typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=37
+ # Show go version only when in a go project subdirectory.
+ typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_GO_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #################[ rust_version: rustc version (https://www.rust-lang.org) ]##################
+ # Rust version color.
+ typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=37
+ # Show rust version only when in a rust project subdirectory.
+ typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_RUST_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###############[ dotnet_version: .NET version (https://dotnet.microsoft.com) ]################
+ # .NET version color.
+ typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=134
+ # Show .NET version only when in a .NET project subdirectory.
+ typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_DOTNET_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #####################[ php_version: php version (https://www.php.net/) ]######################
+ # PHP version color.
+ typeset -g POWERLEVEL9K_PHP_VERSION_FOREGROUND=99
+ # Show PHP version only when in a PHP project subdirectory.
+ typeset -g POWERLEVEL9K_PHP_VERSION_PROJECT_ONLY=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PHP_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##########[ laravel_version: laravel php framework version (https://laravel.com/) ]###########
+ # Laravel version color.
+ typeset -g POWERLEVEL9K_LARAVEL_VERSION_FOREGROUND=161
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LARAVEL_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ####################[ java_version: java version (https://www.java.com/) ]####################
+ # Java version color.
+ typeset -g POWERLEVEL9K_JAVA_VERSION_FOREGROUND=32
+ # Show java version only when in a java project subdirectory.
+ typeset -g POWERLEVEL9K_JAVA_VERSION_PROJECT_ONLY=true
+ # Show brief version.
+ typeset -g POWERLEVEL9K_JAVA_VERSION_FULL=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_JAVA_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###[ package: name@version from package.json (https://docs.npmjs.com/files/package.json) ]####
+ # Package color.
+ typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=117
+ # Package format. The following parameters are available within the expansion.
+ #
+ # - P9K_PACKAGE_NAME The value of `name` field in package.json.
+ # - P9K_PACKAGE_VERSION The value of `version` field in package.json.
+ #
+ # typeset -g POWERLEVEL9K_PACKAGE_CONTENT_EXPANSION='${P9K_PACKAGE_NAME//\%/%%}@${P9K_PACKAGE_VERSION//\%/%%}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PACKAGE_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #############[ rbenv: ruby version from rbenv (https://github.com/rbenv/rbenv) ]##############
+ # Rbenv color.
+ typeset -g POWERLEVEL9K_RBENV_FOREGROUND=168
+ # Hide ruby version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global)
+ # If set to false, hide ruby version if it's the same as global:
+ # $(rbenv version-name) == $(rbenv global).
+ typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide ruby version if it's equal to "system".
+ typeset -g POWERLEVEL9K_RBENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_RBENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #######################[ rvm: ruby version from rvm (https://rvm.io) ]########################
+ # Rvm color.
+ typeset -g POWERLEVEL9K_RVM_FOREGROUND=168
+ # Don't show @gemset at the end.
+ typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false
+ # Don't show ruby- at the front.
+ typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_RVM_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###########[ fvm: flutter version management (https://github.com/leoafarias/fvm) ]############
+ # Fvm color.
+ typeset -g POWERLEVEL9K_FVM_FOREGROUND=38
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_FVM_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##########[ luaenv: lua version from luaenv (https://github.com/cehoffman/luaenv) ]###########
+ # Lua color.
+ typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=32
+ # Hide lua version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global)
+ # If set to false, hide lua version if it's the same as global:
+ # $(luaenv version-name) == $(luaenv global).
+ typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide lua version if it's equal to "system".
+ typeset -g POWERLEVEL9K_LUAENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LUAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###############[ jenv: java version from jenv (https://github.com/jenv/jenv) ]################
+ # Java color.
+ typeset -g POWERLEVEL9K_JENV_FOREGROUND=32
+ # Hide java version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global)
+ # If set to false, hide java version if it's the same as global:
+ # $(jenv version-name) == $(jenv global).
+ typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide java version if it's equal to "system".
+ typeset -g POWERLEVEL9K_JENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_JENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###########[ plenv: perl version from plenv (https://github.com/tokuhirom/plenv) ]############
+ # Perl color.
+ typeset -g POWERLEVEL9K_PLENV_FOREGROUND=67
+ # Hide perl version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global)
+ # If set to false, hide perl version if it's the same as global:
+ # $(plenv version-name) == $(plenv global).
+ typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide perl version if it's equal to "system".
+ typeset -g POWERLEVEL9K_PLENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###########[ perlbrew: perl version from perlbrew (https://github.com/gugod/App-perlbrew) ]############
+ # Perlbrew color.
+ typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67
+ # Show perlbrew version only when in a perl project subdirectory.
+ typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true
+ # Don't show "perl-" at the front.
+ typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PERLBREW_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############
+ # PHP color.
+ typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99
+ # Hide php version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global)
+ # If set to false, hide php version if it's the same as global:
+ # $(phpenv version-name) == $(phpenv global).
+ typeset -g POWERLEVEL9K_PHPENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide php version if it's equal to "system".
+ typeset -g POWERLEVEL9K_PHPENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
+ # Scala color.
+ typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160
+ # Hide scala version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
+ # If set to false, hide scala version if it's the same as global:
+ # $(scalaenv version-name) == $(scalaenv global).
+ typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide scala version if it's equal to "system".
+ typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
+ # Haskell color.
+ typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172
+ # Hide haskell version if it doesn't come from one of these sources.
+ #
+ # shell: version is set by STACK_YAML
+ # local: version is set by stack.yaml up the directory tree
+ # global: version is set by the implicit global project (~/.stack/global-project/stack.yaml)
+ typeset -g POWERLEVEL9K_HASKELL_STACK_SOURCES=(shell local)
+ # If set to false, hide haskell version if it's the same as in the implicit global project.
+ typeset -g POWERLEVEL9K_HASKELL_STACK_ALWAYS_SHOW=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
+ # Show kubecontext only when the command you are typing invokes one of these tools.
+ # Tip: Remove the next line to always show kubecontext.
+ typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl'
+
+ # Kubernetes context classes for the purpose of using different colors, icons and expansions with
+ # different contexts.
+ #
+ # POWERLEVEL9K_KUBECONTEXT_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current kubernetes context gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_KUBECONTEXT_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_KUBECONTEXT_CLASSES defines the context class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' DEFAULT)
+ #
+ # If your current kubernetes context is "deathray-testing/default", its class is TEST
+ # because "deathray-testing/default" doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' DEFAULT)
+ typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=134
+ # typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ # Use POWERLEVEL9K_KUBECONTEXT_CONTENT_EXPANSION to specify the content displayed by kubecontext
+ # segment. Parameter expansions are very flexible and fast, too. See reference:
+ # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion.
+ #
+ # Within the expansion the following parameters are always available:
+ #
+ # - P9K_CONTENT The content that would've been displayed if there was no content
+ # expansion defined.
+ # - P9K_KUBECONTEXT_NAME The current context's name. Corresponds to column NAME in the
+ # output of `kubectl config get-contexts`.
+ # - P9K_KUBECONTEXT_CLUSTER The current context's cluster. Corresponds to column CLUSTER in the
+ # output of `kubectl config get-contexts`.
+ # - P9K_KUBECONTEXT_NAMESPACE The current context's namespace. Corresponds to column NAMESPACE
+ # in the output of `kubectl config get-contexts`. If there is no
+ # namespace, the parameter is set to "default".
+ # - P9K_KUBECONTEXT_USER The current context's user. Corresponds to column AUTHINFO in the
+ # output of `kubectl config get-contexts`.
+ #
+ # If the context points to Google Kubernetes Engine (GKE) or Elastic Kubernetes Service (EKS),
+ # the following extra parameters are available:
+ #
+ # - P9K_KUBECONTEXT_CLOUD_NAME Either "gke" or "eks".
+ # - P9K_KUBECONTEXT_CLOUD_ACCOUNT Account/project ID.
+ # - P9K_KUBECONTEXT_CLOUD_ZONE Availability zone.
+ # - P9K_KUBECONTEXT_CLOUD_CLUSTER Cluster.
+ #
+ # P9K_KUBECONTEXT_CLOUD_* parameters are derived from P9K_KUBECONTEXT_CLUSTER. For example,
+ # if P9K_KUBECONTEXT_CLUSTER is "gke_my-account_us-east1-a_my-cluster-01":
+ #
+ # - P9K_KUBECONTEXT_CLOUD_NAME=gke
+ # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=my-account
+ # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east1-a
+ # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01
+ #
+ # If P9K_KUBECONTEXT_CLUSTER is "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster-01":
+ #
+ # - P9K_KUBECONTEXT_CLOUD_NAME=eks
+ # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=123456789012
+ # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east-1
+ # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01
+ typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION=
+ # Show P9K_KUBECONTEXT_CLOUD_CLUSTER if it's not empty and fall back to P9K_KUBECONTEXT_NAME.
+ POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}'
+ # Append the current context's namespace if it's not "default".
+ POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}'
+
+ # Custom prefix.
+ typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat '
+
+ ################[ terraform: terraform workspace (https://www.terraform.io) ]#################
+ # Don't show terraform workspace if it's literally "default".
+ typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
+ # POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current terraform workspace gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_TERRAFORM_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_TERRAFORM_CLASSES defines the workspace class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' OTHER)
+ #
+ # If your current terraform workspace is "project_test", its class is TEST because "project_test"
+ # doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_TERRAFORM_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_TERRAFORM_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_TERRAFORM_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' OTHER)
+ typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38
+ # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #############[ terraform_version: terraform version (https://www.terraform.io) ]##############
+ # Terraform version color.
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
+ # Show aws only when the command you are typing invokes one of these tools.
+ # Tip: Remove the next line to always show aws.
+ typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt'
+
+ # POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current AWS profile gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_AWS_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_AWS_CLASSES defines the profile class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_AWS_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' DEFAULT)
+ #
+ # If your current AWS profile is "company_test", its class is TEST
+ # because "company_test" doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_AWS_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_AWS_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_AWS_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_AWS_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' DEFAULT)
+ typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208
+ # typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ # AWS segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_AWS_PROFILE The name of the current AWS profile.
+ # - P9K_AWS_REGION The region associated with the current AWS profile.
+ typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
+
+ #[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
+ # AWS Elastic Beanstalk environment color.
+ typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
+ # Show azure only when the command you are typing invokes one of these tools.
+ # Tip: Remove the next line to always show azure.
+ typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
+
+ # POWERLEVEL9K_AZURE_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current azure account name gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_AZURE_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_AZURE_CLASSES defines the account class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' OTHER)
+ #
+ # If your current azure account is "company_test", its class is TEST because "company_test"
+ # doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_AZURE_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_AZURE_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' OTHER)
+
+ # Azure account name color.
+ typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=32
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_AZURE_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]###########
+ # Show gcloud only when the command you are typing invokes one of these tools.
+ # Tip: Remove the next line to always show gcloud.
+ typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil'
+ # Google cloud color.
+ typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32
+
+ # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or
+ # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative
+ # enough. You can use the following parameters in the expansions. Each of them corresponds to the
+ # output of `gcloud` tool.
+ #
+ # Parameter | Source
+ # -------------------------|--------------------------------------------------------------------
+ # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)'
+ # P9K_GCLOUD_ACCOUNT | gcloud config get-value account
+ # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
+ # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
+ #
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
+ #
+ # Obtaining project name requires sending a request to Google servers. This can take a long time
+ # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
+ # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets
+ # set and gcloud prompt segment transitions to state COMPLETE.
+ #
+ # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL
+ # and COMPLETE. You can also hide gcloud in state PARTIAL by setting
+ # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and
+ # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty.
+ typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
+ typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
+
+ # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name
+ # this often. Negative value disables periodic polling. In this mode project name is retrieved
+ # only when the current configuration, account or project id changes.
+ typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
+ # Show google_app_cred only when the command you are typing invokes one of these tools.
+ # Tip: Remove the next line to always show google_app_cred.
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
+
+ # Google application credentials classes for the purpose of using different colors, icons and
+ # expansions with different credentials.
+ #
+ # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES is an array with even number of elements. The first
+ # element in each pair defines a pattern against which the current kubernetes context gets
+ # matched. More specifically, it's P9K_CONTENT prior to the application of context expansion
+ # (see below) that gets matched. If you unset all POWERLEVEL9K_GOOGLE_APP_CRED_*CONTENT_EXPANSION
+ # parameters, you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES defines the context class. Patterns are tried in order.
+ # The first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=(
+ # '*:*prod*:*' PROD
+ # '*:*test*:*' TEST
+ # '*' DEFAULT)
+ #
+ # If your current Google application credentials is "service_account deathray-testing x@y.com",
+ # its class is TEST because it doesn't match the pattern '* *prod* *' but does match '* *test* *'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_CONTENT_EXPANSION='$P9K_GOOGLE_APP_CRED_PROJECT_ID'
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=(
+ # '*:*prod*:*' PROD # These values are examples that are unlikely
+ # '*:*test*:*' TEST # to match your needs. Customize them as needed.
+ '*' DEFAULT)
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=32
+ # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ # Use POWERLEVEL9K_GOOGLE_APP_CRED_CONTENT_EXPANSION to specify the content displayed by
+ # google_app_cred segment. Parameter expansions are very flexible and fast, too. See reference:
+ # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion.
+ #
+ # You can use the following parameters in the expansion. Each of them corresponds to one of the
+ # fields in the JSON file pointed to by GOOGLE_APPLICATION_CREDENTIALS.
+ #
+ # Parameter | JSON key file field
+ # ---------------------------------+---------------
+ # P9K_GOOGLE_APP_CRED_TYPE | type
+ # P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
+ # P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
+ #
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
+
+ ##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
+ # Toolbox color.
+ typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178
+ # Don't display the name of the toolbox if it matches fedora-toolbox-*.
+ typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # Custom prefix.
+ typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%fin '
+
+ ###############################[ public_ip: public IP address ]###############################
+ # Public IP color.
+ typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PUBLIC_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ########################[ vpn_ip: virtual private network indicator ]#########################
+ # VPN IP color.
+ typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=81
+ # When on VPN, show just an icon without the IP address.
+ # Tip: To display the private IP address when on VPN, remove the next line.
+ typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
+ # Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
+ # to see the name of the interface.
+ typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)'
+ # If set to true, show one segment per matching network interface. If set to false, show only
+ # one segment corresponding to the first matching network interface.
+ # Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
+ typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_VPN_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ###########[ ip: ip address and bandwidth usage for a specified network interface ]###########
+ # IP color.
+ typeset -g POWERLEVEL9K_IP_FOREGROUND=38
+ # The following parameters are accessible within the expansion:
+ #
+ # Parameter | Meaning
+ # ----------------------+-------------------------------------------
+ # P9K_IP_IP | IP address
+ # P9K_IP_INTERFACE | network interface
+ # P9K_IP_RX_BYTES | total number of bytes received
+ # P9K_IP_TX_BYTES | total number of bytes sent
+ # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
+ # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
+ # P9K_IP_RX_RATE | receive rate (since last prompt)
+ # P9K_IP_TX_RATE | send rate (since last prompt)
+ typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215F⇡$P9K_IP_TX_RATE}'
+ # Show information for the first network interface whose name matches this regular expression.
+ # Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
+ typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ #########################[ proxy: system-wide http/https/ftp proxy ]##########################
+ # Proxy color.
+ typeset -g POWERLEVEL9K_PROXY_FOREGROUND=68
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PROXY_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ ################################[ battery: internal battery ]#################################
+ # Show battery in red when it's below this level and not connected to power supply.
+ typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20
+ typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=160
+ # Show battery in green when it's charging or fully charged.
+ typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=70
+ # Show battery in yellow when it's discharging.
+ typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=178
+ # Battery pictograms going from low to high level of charge.
+ typeset -g POWERLEVEL9K_BATTERY_STAGES='\UF008E\UF007A\UF007B\UF007C\UF007D\UF007E\UF007F\UF0080\UF0081\UF0082\UF0079'
+ # Don't show the remaining time to charge/discharge.
+ typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false
+
+ #####################################[ wifi: wifi speed ]#####################################
+ # WiFi color.
+ typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ # Use different colors and icons depending on signal strength ($P9K_WIFI_BARS).
+ #
+ # # Wifi colors and icons for different signal strength levels (low to high).
+ # typeset -g my_wifi_fg=(68 68 68 68 68) # <-- change these values
+ # typeset -g my_wifi_icon=('WiFi' 'WiFi' 'WiFi' 'WiFi' 'WiFi') # <-- change these values
+ #
+ # typeset -g POWERLEVEL9K_WIFI_CONTENT_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}$P9K_WIFI_LAST_TX_RATE Mbps'
+ # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}${my_wifi_icon[P9K_WIFI_BARS+1]}'
+ #
+ # The following parameters are accessible within the expansions:
+ #
+ # Parameter | Meaning
+ # ----------------------+---------------
+ # P9K_WIFI_SSID | service set identifier, a.k.a. network name
+ # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
+ # P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
+ # P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
+ # P9K_WIFI_NOISE | noise in dBm, from -120 to 0
+ # P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
+
+ ####################################[ time: current time ]####################################
+ # Current time color.
+ typeset -g POWERLEVEL9K_TIME_FOREGROUND=66
+ # Format for the current time: 09:51:02. See `man 3 strftime`.
+ typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
+ # If set to true, time will update when you hit enter. This way prompts for the past
+ # commands will contain the start times of their commands as opposed to the default
+ # behavior where they contain the end times of their preceding commands.
+ typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TIME_VISUAL_IDENTIFIER_EXPANSION='⭐'
+ # Custom prefix.
+ typeset -g POWERLEVEL9K_TIME_PREFIX='%fat '
+
+ # Example of a user-defined prompt segment. Function prompt_example will be called on every
+ # prompt if `example` prompt segment is added to POWERLEVEL9K_LEFT_PROMPT_ELEMENTS or
+ # POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS. It displays an icon and orange text greeting the user.
+ #
+ # Type `p10k help segment` for documentation and a more sophisticated example.
+ function prompt_example() {
+ p10k segment -f 208 -i '⭐' -t 'hello, %n'
+ }
+
+ # User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
+ # is to generate the prompt segment for display in instant prompt. See
+ # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ #
+ # Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
+ # and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
+ # will replay these calls without actually calling instant_prompt_*. It is imperative that
+ # instant_prompt_* always makes the same `p10k segment` calls regardless of environment. If this
+ # rule is not observed, the content of instant prompt will be incorrect.
+ #
+ # Usually, you should either not define instant_prompt_* or simply call prompt_* from it. If
+ # instant_prompt_* is not defined for a segment, the segment won't be shown in instant prompt.
+ function instant_prompt_example() {
+ # Since prompt_example always makes the same `p10k segment` calls, we can call it from
+ # instant_prompt_example. This will give us the same `example` prompt segment in the instant
+ # and regular prompts.
+ prompt_example
+ }
+
+ # User-defined prompt segments can be customized the same way as built-in segments.
+ # typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=208
+ # typeset -g POWERLEVEL9K_EXAMPLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
+
+ # Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt
+ # when accepting a command line. Supported values:
+ #
+ # - off: Don't change prompt when accepting a command line.
+ # - always: Trim down prompt when accepting a command line.
+ # - same-dir: Trim down prompt when accepting a command line unless this is the first command
+ # typed after changing current working directory.
+ typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=always
+
+ # Instant prompt mode.
+ #
+ # - off: Disable instant prompt. Choose this if you've tried instant prompt and found
+ # it incompatible with your zsh configuration files.
+ # - quiet: Enable instant prompt and don't print warnings when detecting console output
+ # during zsh initialization. Choose this if you've read and understood
+ # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # - verbose: Enable instant prompt and print a warning when detecting console output during
+ # zsh initialization. Choose this if you've never tried instant prompt, haven't
+ # seen the warning, or if you are unsure what this all means.
+ typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
+
+ # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
+ # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
+ # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
+ # really need it.
+ typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
+
+ # If p10k is already loaded, reload configuration.
+ # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
+ (( ! $+functions[p10k] )) || p10k reload
+}
+
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
+(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
+'builtin' 'unset' 'p10k_config_opts'
diff --git a/ar/.config/zsh/packages.zsh b/ar/.config/zsh/packages.zsh
new file mode 100644
index 0000000..d00584e
--- /dev/null
+++ b/ar/.config/zsh/packages.zsh
@@ -0,0 +1,22 @@
+#!/bin/zsh
+
+### --- Packages --- ###
+typeset -A packages
+packages=(
+ atuin "--disable-up-arrow"
+ batman "--export-env"
+ zoxide "--cmd cd --hook prompt"
+)
+
+### --- Eval Function --- ###
+eval_packages() {
+ for package in ${(k)packages}; do
+ if command -v "$package" >/dev/null; then
+ local args=(${(s: :)packages[$package]})
+ [[ ${#args[@]} -gt 0 ]] && eval "$($package init zsh ${args[@]})" || eval "$($package init zsh)"
+ fi
+ done
+}
+
+### --- Init --- ###
+eval_packages
diff --git a/ar/.config/zsh/plugins.zsh b/ar/.config/zsh/plugins.zsh
new file mode 100644
index 0000000..a9d17be
--- /dev/null
+++ b/ar/.config/zsh/plugins.zsh
@@ -0,0 +1,101 @@
+#!/bin/zsh
+
+### --- Plugins --- ###
+plugins=(
+ "Aloxaf/fzf-tab"
+ # "jeffreytse/zsh-vi-mode"
+ "kutsan/zsh-system-clipboard"
+ # "MichaelAquilina/zsh-you-should-use"
+ # "marlonrichert/zsh-autocomplete"
+ "ohmyzsh/command-not-found"
+ #"ohmyzsh/sudo"
+ # "romkatv/powerlevel10k"
+ "wfxr/forgit"
+ "zdharma-continuum/fast-syntax-highlighting"
+ "zsh-users/zsh-autosuggestions"
+ "zsh-users/zsh-completions"
+)
+
+### --- Source Plugins --- ###
+# Check plugins
+zsh_check_plugins() {
+ installed="true"
+ for plugin in "${plugins[@]}"; do
+ PLUGIN_NAME=$(echo "$plugin" | cut -d '/' -f 2)
+ [ "$ZPLUGINDIR" = "ohmyzsh" ] && ZPLUGINDIR="$PLUGIN_NAME"
+ PLUGIN_PATH="$ZPLUGINDIR/$PLUGIN_NAME"
+ [ -d "$PLUGIN_PATH" ] && zsh_source_plugin "$PLUGIN_NAME/$PLUGIN_NAME" || { installed="false"; break; }
+ done
+ [ "$installed" = "true" ] || zsh_add_plugins "${plugins[@]}"
+}
+
+# Function to source plugin files
+zsh_source_plugin() {
+ for file in "$@"; do
+ if [ -f "$ZPLUGINDIR/$file.plugin.zsh" ] && echo "$file" | grep -vq "command-not-found/command-not-found"; then
+ . "$ZPLUGINDIR/$file.plugin.zsh"
+ elif echo "$file" | grep -q "command-not-found/command-not-found"; then
+ . "$ZPLUGINDIR/command-not-found/command-not-found.plugin.zsh"
+ elif [ -z "$ZPLUGINDIR/$file.plugin.zsh" ] && [ -f "$ZPLUGINDIR/$file.zsh" ]; then
+ . "$ZPLUGINDIR/$file.zsh"
+ fi
+ [ -f "$ZPLUGINDIR/$file.zsh-theme" ] && . "$ZPLUGINDIR/$file.zsh-theme" && . "${XDG_CONFIG_HOME:-${HOME}/.config}/shell/p10k"
+ done
+}
+
+# Function to add plugins
+zsh_add_plugins() {
+ for plugin do
+ PLUGIN_NAME=$(echo "$plugin" | cut -d '/' -f 2)
+ PLUGIN_PATH="$ZPLUGINDIR/$PLUGIN_NAME"
+
+ if [ -d "$PLUGIN_PATH" ]; then
+ zsh_source_plugin "$PLUGIN_NAME/$PLUGIN_NAME"
+ else
+ ORG_NAME=$(echo "$plugin" | cut -d '/' -f 1)
+ case "$ORG_NAME" in
+ "ohmyzsh")
+ [ ! -d "$ZPLUGINDIR/oh-my-zsh" ] && git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "$ZPLUGINDIR/oh-my-zsh" >/dev/null 2>&1
+
+ OHMYZSH_PLUGIN_PATH="$ZPLUGINDIR/oh-my-zsh/plugins/$PLUGIN_NAME"
+ cp -r "$OHMYZSH_PLUGIN_PATH" "$ZPLUGINDIR/" >/dev/null 2>&1
+ rm -rf "$ZPLUGINDIR/oh-my-zsh" >/dev/null 2>&1
+ ;;
+ *)
+ git clone "https://github.com/$plugin.git" "$PLUGIN_PATH" >/dev/null 2>&1
+ ;;
+ esac
+ rm -rf "$PLUGIN_PATH/.git"
+ chmod +x "$PLUGIN_PATH"
+ fi
+ done
+}
+
+# Function to sync plugins
+zsh_sync_plugins() {
+ ACTIVE_PLUGINS=$(grep '^[[:space:]]*"[^"]\+"' ~/.config/zsh/plugins.zsh | sed 's|.*/\([^/"]*\)".*|\1|')
+
+ for PLUGIN_DIR in "$ZPLUGINDIR"/*; do
+ if [ -d "$PLUGIN_DIR" ]; then
+ PLUGIN_NAME=$(basename "$PLUGIN_DIR")
+
+ echo "$ACTIVE_PLUGINS" | grep -q "$PLUGIN_NAME" || {
+ echo "Removing unused plugin: $PLUGIN_NAME"
+ rm -rf "$PLUGIN_DIR"
+ }
+ fi
+ done
+}
+
+# Function to update plugins
+# Since .git folder in each plugin dir is removed,
+# Delete all plugins and install them agian
+# .git is searched in Neovim projects
+alias zup=zsh_update_plugins
+zsh_update_plugins() {
+ [ -d "$ZPLUGINDIR" ] && rm -rf "$ZPLUGINDIR" && zsh_check_plugins "${plugins[@]}"
+ zsh_sync_plugins
+}
+
+zsh_check_plugins "${plugins[@]}"
+zsh_sync_plugins
diff --git a/ar/.config/zsh/scripts.zsh b/ar/.config/zsh/scripts.zsh
new file mode 100644
index 0000000..07e9c87
--- /dev/null
+++ b/ar/.config/zsh/scripts.zsh
@@ -0,0 +1,836 @@
+#!/bin/zsh
+
+###########################################################################################
+###########################################################################################
+### --- ALIAS --- ###
+# find aliases
+alias ali=fzf_aliases
+function fzf_aliases() {
+ local aliases=$(alias)
+ local max_length=$(echo "$aliases" | cut -d'=' -f1 | awk '{print length}' | sort -nr | head -n 1)
+ [ "$max_length" -gt 20 ] && max_length=20
+ format_aliases() {
+ echo "$aliases" | while IFS= read -r line; do
+ alias_name=$(echo "$line" | cut -d'=' -f1)
+ alias_command=$(echo "$line" | cut -d'=' -f2- | sed "s/^'//;s/'$//")
+ printf "%-${max_length}s = %s\n" "$alias_name" "$alias_command"
+ done
+ }
+ alias_file="${XDG_CONFIG_HOME:-${HOME}/.config}/shell/aliasrc"
+ selected_alias=$(format_aliases | fzf --header="select an alias")
+ # check if an alias was selected
+ if [ -n "$selected_alias" ]; then
+ # extract the alias name
+ alias_name=$(echo "$selected_alias" | cut -d'=' -f1 | xargs)
+
+ # get the line number from the alias file
+ line_number=$(grep -n "^alias $alias_name=" "$alias_file" | cut -d':' -f1)
+
+ # open nvim at the specified line
+ if [ -n "$line_number" ]; then
+ nvim "+$line_number" "$alias_file"
+ else
+ scripts_file="${ZDOTDIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/zsh}/7-scripts.zsh"
+ line_number=$(grep -n "^alias $alias_name=" "$scripts_file" | cut -d':' -f1)
+ nvim "+$line_number" "$scripts_file"
+ fi
+ fi
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- COLOR --- ###
+# print color
+alias pcol=print_col
+function print_col() {
+ awk 'BEGIN{
+s="/\\/\\/\\/\\/\\"; s=s s s s s s s s;
+function for (colnum = 0; colnum<77; colnum++) {
+ r = 255-(colnum*255/76);
+ g = (colnum*510/76);
+ b = (colnum*255/76);
+ if (g>255) g = 510-g;
+ printf "\033[48;2;%d;%d;%dm", r,g,b;
+ printf "\033[38;2;%d;%d;%dm", 255-r,255-g,255-b;
+ printf "%s\033[0m", substr(s,colnum+1,1);
+}
+printf "\n";
+ }'
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- COMMAND OUTPUT --- ###
+# print last command output
+alias ilco=insert_last_command_output
+function insert_last_command_output() { LBUFFER+="$(eval $history[$((HISTCMD-1))])"; }
+
+
+###########################################################################################
+###########################################################################################
+### --- CONFIG --- ###
+# fzf config
+alias fcfg=fzf_config
+function fzf_config() {
+ [ $# -gt 0 ] && zoxide query -i "$1" | xargs "${EDITOR}" && return
+ local file
+ file="$(zoxide query -l | fzf --cycle -1 -0 --no-sort +m)" && cd "${file}" || return 1
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- COPY --- ###
+# copy file name to clipboard
+alias cpfn=copy_filename
+function copy_filename() {
+ if ! command -v xclip >/dev/null; then
+ echo "Error: 'xclip' is not installed." >&2
+ return 1
+ fi
+
+ if ! command -v fzf >/dev/null; then
+ echo "Error: 'fzf' is not installed." >&2
+ return 1
+ fi
+
+ local file="$1"
+
+ # If no argument is provided, use fzf to select a file
+ if [ -z "$file" ]; then
+ file=$(fzf --cycle --preview "cat {}")
+ fi
+
+ # Check if a file was found or selected
+ if [ -n "$file" ]; then
+ local filename=$(basename "$file")
+ echo -n "$filename" | xclip -selection clipboard
+ echo "Filename copied to clipboard: $filename"
+ else
+ echo "No file selected."
+ fi
+}
+
+# copy file contents
+alias cpfc=copy_contents
+function copy_contents() {
+ if ! command -v xclip >/dev/null; then
+ echo "Error: 'xclip' is not installed." >&2
+ return 1
+ fi
+
+ if ! command -v fzf >/dev/null; then
+ echo "Error: 'fzf' is not installed." >&2
+ return 1
+ fi
+
+ local file="$1"
+
+ # If no argument is provided, use fzf to select a file
+ if [ -z "$file" ]; then
+ file=$(fzf --cycle --preview "cat {}")
+ fi
+
+ # Check if a file was found or selected
+ if [ -n "$file" ]; then
+ # Use `sed` to delete only the last newline character
+ cat "$file" | sed ':a;N;$!ba;s/\n$//' | xclip -selection clipboard
+ echo "Contents of '$file' copied to clipboard."
+ else
+ echo "No file selected."
+ fi
+}
+
+# copy the current working directory path to the clipboard
+alias cpcp=copy_current_path
+function copy_current_path() {
+ if command -v xclip >/dev/null; then
+ printf "%s" "$PWD" | xclip -selection clipboard
+ printf "%s\n" "Current working directory '$(basename "$PWD")' path copied to clipboard."
+ else
+ printf "%s\n" "Error: 'xclip' command not found. Please install 'xclip' to use this function."
+ fi
+}
+
+# copy file real path
+alias cprp=copy_real_path
+function copy_real_path() {
+ if ! command -v xclip >/dev/null; then
+ echo "Error: 'xclip' is not installed." >&2
+ return 1
+ fi
+
+ if ! command -v fzf >/dev/null; then
+ echo "Error: 'fzf' is not installed." >&2
+ return 1
+ fi
+
+ local file="$1"
+
+ # If no argument is provided, use fzf to select a file
+ if [ -z "$file" ]; then
+ file=$(fzf --cycle --preview "cat {}")
+ fi
+
+ # Check if a file was found or selected
+ if [ -n "$file" ]; then
+ local full_path=$(realpath "$file")
+ echo -n "$full_path" | xclip -selection clipboard
+ echo "File path copied to clipboard: $full_path"
+ else
+ echo "No file selected."
+ fi
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- CREATE --- ###
+# mkdir && cd
+alias mc=mkcd
+function mkcd() { mkdir -p "$@" && cd "$_"; }
+
+# create dir with current date
+function mkdt () { mkdir -p ${1:+$1$prefix_separator}"$(date +%F)"; }
+
+
+###########################################################################################
+###########################################################################################
+### --- DOCKER --- ###
+# select a docker container to start and attach to
+alias doca=docker_container_init
+function docker_container_init() {
+ local cid
+ cid=$(docker ps -a | sed 1d | fzf --cycle -1 -q "$1" | awk '{print $1}')
+
+ [ -n "$cid" ] && docker start "$cid" && docker attach "$cid"
+}
+
+# select a running docker container to stop
+alias docs=docker_stop
+function docker_stop() {
+ local cid
+ cid=$(docker ps | sed 1d | fzf --cycle -q "$1" | awk '{print $1}')
+
+ [ -n "$cid" ] && docker stop "$cid"
+}
+
+# select a docker container or containers to remove
+alias docrmc=docker_remove_containers
+function docker_remove_containers() { docker ps -a | sed 1d | fzf --cycle -q "$1" --no-sort -m --tac | awk '{ print $1 }' | xargs -r docker rm; }
+
+# select a docker image or images to remove
+alias docrmi=docker_remove_images
+function docker_remove_images() { docker images | sed 1d | fzf --cycle -q "$1" --no-sort -m --tac | awk '{ print $3 }' | xargs -r docker rmi; }
+
+
+###########################################################################################
+###########################################################################################
+### --- ECRYPTFS --- ###
+# mount ecryptfs
+alias emt=ecryptfs_mount
+function ecryptfs_mount() {
+ ! mount | grep -q " $1 " && echo "$(pass show encryption/ecryptfs)" | sudo mount -t ecryptfs "$1" "$2" \
+ -o ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=no,ecryptfs_enable_filename_crypto=yes,ecryptfs_sig="$(sudo cat /root/.ecryptfs/sig-cache.txt)",ecryptfs_fnek_sig="$(sudo cat /root/.ecryptfs/sig-cache.txt)",passwd="$(pass show encryption/ecryptfs)" >/dev/null 2>&1 &&
+ echo "'$2' folder is mounted!"
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- GIT --- ###
+# TheSiahxyz's git repos
+alias gcggg=thesiahxyz_git
+function thesiahxyz_git() {
+ choice=$(ssh "$THESIAH_GIT" "ls -a | grep -i \".*\\.git$\"" | fzf --cycle --prompt="  " --height=50% --layout=reverse --border --exit-0)
+ [ -n "$choice" ] && [ -n "$1" ] && git clone "${THESIAH_GIT:-git@${THESIAH:-thesiah.xyz}}":"$choice" "$1" ||
+ [ -n "$choice" ] && git clone "${THESIAH_GIT:-git@${THESIAH:-thesiah.xyz}}":"$choice"
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- GOTO --- ###
+# go to the path stored in the clipboard
+alias cdp=cd_clipboard_path
+function cd_clipboard_path() {
+ if command -v xclip >/dev/null; then
+ local target_dir
+ target_dir="$(xclip -o -sel clipboard)"
+ if [[ -d "${target_dir}" ]]; then
+ cd "${target_dir}" && printf "%s\n" "Changed directory to: ${target_dir}"
+ else
+ printf "%s\n" "Error: Invalid directory path or directory does not exist."
+ fi
+ else
+ printf "%s\n" "Error: 'xclip' command not found. Please install 'xclip' to use this function."
+ fi
+}
+
+# fzf directory and go to the parent directory
+alias fD=fzf_directory
+function fzf_directory() {
+ dirs="$(find "$HOME" -type d \( -path "**/.git/*" -o -path "**/.cache/*" -o -path "**/yay/*" -o -path "$HOME/.local/bin/zsh" \) -prune -o -type d -print | fzf --multi)"
+ [ -d "$dirs" ] && cd "$dirs" && ls -A || opensessions "$dirs"
+}
+
+# search scripts in ~/.local/bin
+alias sscs=search_scripts
+function search_scripts() {
+ choice="$(find ~/.local/bin -mindepth 1 \( -type f -o -type l \) -not -name '*.md' -not -path '*/zsh/*' -printf '%P\n' | fzf --cycle)"
+ ([ -n "$choice" ] && [ -f "$HOME/.local/bin/$choice" ]) && ${EDITOR:-nvim} "$HOME/.local/bin/$choice"
+}
+
+# check git status by directories in specific path
+alias fgst=fetch_git_repos_status
+function fetch_git_repos_status() {
+ SELECTED_DIRS=$(bash << 'EOF'
+ # Source the Git prompt script to get access to __git_ps1
+ source /usr/share/git/completion/git-prompt.sh
+
+ # Enable symbols for dirty state, untracked files, and branch ahead/behind
+ export GIT_PS1_SHOWDIRTYSTATE="auto"
+ export GIT_PS1_SHOWUNTRACKEDFILES="auto"
+ export GIT_PS1_SHOWUPSTREAM="auto"
+
+ # Define an array of target directories
+ TARGET_DIRECTORIES=(
+ "$HOME/.dotfiles"
+ "$HOME/.local/share/.password-store"
+ "$HOME/.local/src/suckless"
+ )
+
+ # Check the directories under the paths. eg. ../repos/*
+ GIT_DIRS=("$HOME/Private/repos" "$HOME/Public/repos")
+
+ # Append all subdirectories under GIT_DIRS that are Git repositories
+ for GIT_DIR in "${GIT_DIRS[@]}"; do
+ if [ -d "$GIT_DIR" ]; then
+ for SUBDIR in "$GIT_DIR"/*; do
+ if [ -d "$SUBDIR/.git" ]; then
+ TARGET_DIRECTORIES+=("$SUBDIR")
+ fi
+ done
+ fi
+ done
+
+ # Create an array to store the output
+ OUTPUT=()
+
+ # Function to colorize only the Git status symbols
+ colorize_git_status() {
+ local status="$1"
+ # Colorize the entire status with a single color
+ echo -e "\033[33m${status}\033[0m" # Apply yellow color to the status
+ }
+
+ update_time() {
+ timestamp_file="${HOME}/.cache/gitreposupdate"
+ current_time=$(date +%s)
+ if [[ -f "$timestamp_file" ]] && (($(cat "$timestamp_file") > (current_time - 86400))); then
+ return 1 # No update needed
+ else
+ echo "$current_time" >"$timestamp_file"
+ return 0 # Update needed
+ fi
+ }
+ update_time && update=true || update=false
+
+ # Loop through each directory and get the Git status
+ for DIR in "${TARGET_DIRECTORIES[@]}"; do
+ if [ -d "$DIR/.git" ]; then
+ cd "$DIR" || continue
+
+ if $update; then
+ if [ "$(dirname $DIR)" = ".password-store" ]; then
+ pass git fetch >/dev/null 2>&1
+ else
+ git fetch --all --prune --jobs=10 >/dev/null 2>&1
+ fi
+ fi
+
+ # Get Git branch and status using __git_ps1
+ GIT_STATUS=$(__git_ps1 "%s")
+
+ # Colorize the Git status
+ COLORED_GIT_STATUS=$(colorize_git_status "$GIT_STATUS")
+
+ # Add formatted output with colored Git status and directory
+ OUTPUT+=("$(printf "%-20s %s" "$COLORED_GIT_STATUS" "$DIR")")
+ fi
+ done
+
+ # Pass the output to fzf with multi-select enabled (-m)
+ SELECTED=$(printf "%s\n" "${OUTPUT[@]}" | fzf -m --ansi --layout=reverse)
+
+ # Filter out lines that do not end with a valid directory path and are not empty
+ echo "$SELECTED" | awk '{if (NF > 1 && system("[ -d \""$NF"\" ]") == 0) print $NF}'
+EOF
+ )
+ [[ -z "${SELECTED_DIRS// }" ]] && return
+ if [[ "$(echo "$SELECTED_DIRS" | wc -l)" -eq 1 ]]; then
+ cd "$SELECTED_DIRS"
+ if [[ -n "$(git -C "$SELECTED_DIRS" status --porcelain)" ]]; then
+ git status --porcelain 2>/dev/null
+ fi
+ else
+ opensessions "$SELECTED_DIRS"
+ fi
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- HELP --- ###
+# help opt colored by bat
+alias bathelp='bat --plain --language=help'
+function help() { "$@" --help 2>&1 | bathelp; }
+
+
+###########################################################################################
+###########################################################################################
+### --- KEYS --- ###
+# list setxkbmap options
+alias xkey=xset_options
+function xset_options() { grep --color -E "$1" /usr/share/X11/xkb/rules/base.lst; }
+
+# print raw xev key events
+alias keys=xev_raw_key_event
+function xev_raw_key_event() {
+ xev -event keyboard | awk '
+ /^KeyPress/,/^KeyRelease/ {
+ if ($0 ~ /keysym/) print $0
+ }'
+}
+
+# print aligned xev key events
+alias key=xev_aligned_key_event
+function xev_aligned_key_event() {
+ xev -event keyboard | awk '
+ /^(KeyPress|KeyRelease)/ {
+ event_type = $1
+ }
+ /keysym/ {
+ gsub(/\),$/, "", $7)
+ printf "%-12s %-3s %s\n", event_type, $4, $7
+ }'
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- KILL --- ###
+# kill process
+alias fpkill=fzf_kill_process
+function fzf_kill_process() {
+ ps aux |
+ fzf --height 40% \
+ --layout=reverse \
+ --header-lines=1 \
+ --prompt="Select process to kill: " \
+ --preview 'echo {}' \
+ --preview-window up:3:hidden:wrap \
+ --bind 'F2:toggle-preview' |
+ awk '{print $2}' |
+ xargs -r bash -c '
+ if ! kill "$1" 2>/dev/null; then
+ echo "Regular kill failed. Attempting with sudo..."
+ sudo kill "$1" || echo "Failed to kill process $1" >&2
+ fi
+ ' --
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- LF --- ###
+# open lf and cd to the file path
+function lfcd () {
+ tmp="$(mktemp -uq)"
+ trap 'rm -f $tmp >/dev/null 2>&1 && trap - HUP INT QUIT TERM PWR EXIT' HUP INT QUIT TERM PWR EXIT
+ lf -last-dir-path="$tmp" "$@"
+ if [ -f "$tmp" ]; then
+ dir="$(cat "$tmp")"
+ [ -d "$dir" ] && [ "$dir" != "$(pwd)" ] && cd "$dir"
+ fi
+}
+
+###########################################################################################
+###########################################################################################
+### --- MAN --- ###
+# color man page
+# alias man=batman
+function man() {
+ if [[ -z "$@" ]]; then
+ if command -v fzf >/dev/null 2>&1; then
+ local page=$(command man -k . | fzf --prompt='Man> ' --exit-0 | awk '{print $1}')
+ if [[ -n $page ]]; then
+ nvim +"Man $page | only"
+ fi
+ fi
+ else
+ batman "$@"
+ fi
+}
+function batman() {
+ BAT_THEME="ansi" command batman "$@"
+ return $?
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- NVIM --- ###
+# rename nvim directory
+alias ctn=rename_nvim_dir
+function rename_nvim_dir() {
+ if [ $# -ne 2 ]; then
+ echo "Usage: ctn <old_suffix> <new_suffix>"
+ return 1
+ fi
+
+ local old_suffix="$1"
+ local new_suffix="$2"
+ local base_name="nvim"
+
+ # Handle the case where the old suffix is '.'
+ [ "$old_suffix" = "." ] && old_suffix=""
+ [ "$new_suffix" = "." ] && new_suffix=""
+
+ # Directories to be renamed
+ local directories=(
+ "$HOME/.config/$base_name"
+ "$HOME/.local/share/$base_name"
+ "$HOME/.local/state/$base_name"
+ "$HOME/.cache/$base_name"
+ )
+
+ for dir in "${directories[@]}"; do
+ if [ -d "$dir$old_suffix" ]; then
+ mv "$dir$old_suffix" "$dir$new_suffix"
+ echo "Renamed $dir$old_suffix to $dir$new_suffix"
+ else
+ echo "Directory $dir$old_suffix does not exist"
+ fi
+ done
+}
+
+# change nvim config
+alias cnf=change_nvim_config_dir
+function change_nvim_config_dir() {
+ local base_dir="${XDG_DOTFILES_DIR:-$HOME/.dotfiles}/$(whereami)/.config" # Base directory for Neovim configs
+ local target_dir="${XDG_CONFIG_HOME:-$HOME/.config}/nvim" # Target directory for active Neovim config
+ local target_share="${XDG_DATA_HOME:-$HOME/.local/share}/nvim" # Neovim"s share directory
+ local target_state="${XDG_STATE_HOME:-$HOME/.local/state}/nvim" # Neovim"s state directory
+ local target_cache="${XDG_CACHE_HOME:-$HOME/.cache}/nvim" # Neovim"s cache directory
+
+ # Explicitly list your configuration options
+ local configs=("Default" "TheSiahxyz" "NvChad" "LazyVim")
+ local selected_dir=$(printf "%s\n" "${configs[@]}" | fzf --cycle --prompt=" Neovim Config  " --height 50% --layout=reverse --border --exit-0)
+
+ # Check if a configuration was selected
+ [[ -z $selected_dir ]] && return 1
+
+ # Default configuration
+ if [[ $selected_dir == "Default" ]]; then
+ echo "Clearing the Neovim configuration directory..."
+ rm -rf "$target_dir" "$target_share" "$target_state" "$target_cache" &>/dev/null
+ echo "Switched to the base Neovim configuration."
+ return 0
+ fi
+
+ # Construct the full path of the selected configuration
+ local config_path="$base_dir/$selected_dir"
+ echo "$config_path"
+
+ # Clear existing configurations if confirmed by the user
+ echo -n "This will overwrite existing configurations. Continue? (y/n) "
+ read reply
+ if [[ $reply =~ ^[Yy]$ ]]; then
+ echo "Clearing existing Neovim configurations..."
+ rm -rf "$target_dir" "$target_share" "$target_state" "$target_cache" &>/dev/null
+ mkdir -p "$target_dir" "$target_share" "$target_state" "$target_cache" &>/dev/null
+ else
+ echo "Operation cancelled."
+ return 2
+ fi
+
+ # Copy the selected configuration to the target directories
+ if [[ -d "$config_path" ]]; then
+ cp -r "$config_path/." "$target_dir" > /dev/null 2>&1
+ echo "Successfully applied $selected_dir configuration."
+ shortcuts >/dev/null
+ else
+ echo "Configuration directory for $selected_dir does not exist."
+ return 3
+ fi
+
+ if [ "$whereami" = "artix" ]; then
+ chown -R "$USER:wheel" "/home/$USER/.config/nvim"
+ fi
+}
+
+# run nvim with target config
+alias vtc=nvim_target_config
+function nvim_target_config() {
+ items=("Default" "TheSiahxyz" "LazyVim" "NvChad")
+ config=$(printf "%s\n" "${items[@]}" | fzf --cycle --prompt=" Neovim Config  " --height=~50% --layout=reverse --border --exit-0)
+ [[ -z $config ]] && return 0
+ NVIM_APPNAME=$config nvim $@
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- PASS --- ###
+# otp
+function pass_otp() { pass otp uri -q $1; }
+
+# otp insert
+function pass_otp_insert() { pass otp insert $1; }
+
+# copy pass qr code
+alias cpqr=pass_qr
+function pass_qr() { qrencode -o "$1".png -t png -Sv 40 < "$1".pgp; }
+
+
+###########################################################################################
+###########################################################################################
+### --- PASTE --- ###
+if ls "${ZPLUGINDIR:-${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/zsh}/zsh-autosuggestions" >/dev/null 2>&1; then
+ autoload -Uz url-quote-magic
+ function pasteinit() {
+ OLD_SELF_INSERT=${${(s.:.)widgets[self-insert]}[2,3]}
+ zle -N self-insert url-quote-magic
+ }
+ function pastefinish() {
+ zle -N self-insert $OLD_SELF_INSERT
+ }
+ zstyle :bracketed-paste-magic paste-init pasteinit
+ zstyle :bracketed-paste-magic paste-finish pastefinish
+fi
+
+
+###########################################################################################
+###########################################################################################
+### --- STOW --- ###
+# run stow script from dotfiles repo
+alias dstw=dotfeils_stw
+function dotfiles_stw() { "${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/$(whereami)/.local/bin/stw"; }
+
+
+###########################################################################################
+###########################################################################################
+### --- SUDO --- ###
+# insert prefix at the beginning of the previous command
+function __command_replace_buffer() {
+ local old=$1 new=$2 space=${2:+ }
+ if [[ $CURSOR -le ${#old} ]]; then
+ BUFFER="${new}${space}${BUFFER#$old }"
+ CURSOR=${#new}
+ else
+ LBUFFER="${new}${space}${LBUFFER#$old }"
+ fi
+}
+
+# manipulate the previous command line
+function pre_cmd() {
+ local prepend_command=$1
+ local EDITOR=${SUDO_EDITOR:-${VISUAL:-$EDITOR}}
+ [[ -z $BUFFER ]] && LBUFFER="$(fc -ln -1)"
+ local WHITESPACE=""
+
+ # Remove leading whitespace and store it for later restoration
+ if [[ ${LBUFFER:0:1} = " " ]]; then
+ WHITESPACE=" "
+ LBUFFER="${LBUFFER:1}"
+ fi
+
+ # Main logic block
+ {
+ local cmd="${${(Az)BUFFER}[1]}"
+ local realcmd="${${(Az)aliases[$cmd]}[1]:-$cmd}"
+ local editorcmd="${${(Az)EDITOR}[1]}"
+ # Check if EDITOR is set, otherwise prepend the command and return
+ if [[ -z "$EDITOR" ]]; then
+ LBUFFER="$prepend_command $LBUFFER"
+ return
+ fi
+ # Check if the command is an editor command
+ is_editor_cmd=false
+ # Check if realcmd matches EDITOR, editorcmd, or their case variations
+ [[ "$realcmd" = (\$EDITOR|$editorcmd|${editorcmd:c}) || "${realcmd:c}" = ($editorcmd|${editorcmd:c}) ]] && is_editor_cmd=true
+ # Check if the real command's executable path matches the editor command
+ builtin which -a "$realcmd" | command grep -Fx -q "$editorcmd" && is_editor_cmd=true
+ # Execute the command replacement if it's an editor command
+ if $is_editor_cmd; then
+ __command_replace_buffer "$cmd" "$prepend_command -e"
+ return
+ fi
+ # Handle various command patterns in BUFFER
+ case "$BUFFER" in
+ $editorcmd\ *) __command_replace_buffer "$editorcmd" "$prepend_command -e" ;;
+ \$EDITOR\ *) __command_replace_buffer '$EDITOR' "$prepend_command -e" ;;
+ ${prepend_command}\ -e\ *) __command_replace_buffer "${prepend_command} -e" "$EDITOR" ;;
+ ${prepend_command}\ *) __command_replace_buffer "${prepend_command}" "" ;;
+ *) LBUFFER="$prepend_command $LBUFFER" ;;
+ esac
+ } always {
+ # Cleanup code: restore leading whitespace and update the command line
+ LBUFFER="${WHITESPACE}${LBUFFER}"
+ zle && zle redisplay # Only run redisplay if zle is enabled
+ }
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- TMUX --- ###
+# tmux init
+alias tit=tmux_init
+function tmux_init() {
+ ! tmux has-session -t "$TERMINAL" 2>/dev/null && tmux new-session -d -s "$TERMINAL" -c "$HOME"
+ [[ -n "$TMUX" ]] && tmux switch-client -t "$TERMINAL" || tmux attach-session -t "$TERMINAL"
+}
+
+# cd tmux session
+alias cds=cd_session_path
+function cd_session_path() { cd "$(tmux display-message -p '#{session_path}')"; }
+
+# kill tmux session
+alias tmk='kill_tmux_sessions'
+function kill_tmux_sessions() {
+ local sessions
+ sessions="$(tmux ls | fzf --cycle --exit-0 --multi)" || return $?
+ local i
+ for i in "${(f@)sessions}"
+ do
+ [[ $i =~ '([^:]*):.*' ]] && {
+ echo "Killing $match[1]"
+ tmux kill-session -t "$match[1]"
+ }
+ done
+}
+
+# switch tmux session
+alias tms='switch_tmux_session'
+function switch_tmux_session() {
+ local session
+ session=$(tmux list-sessions -F "#{session_name}" \
+ | fzf --cycle --query="$1" --select-1 --exit-0) &&
+ tmux switch-client -t "$session"
+}
+
+
+###########################################################################################
+###########################################################################################
+### --- VIRTUAL ENV --- ###
+# create venvs
+alias createv=create_venv
+function create_venv() {
+ local env_dir="${XDG_DATA_HOME:-${HOME}/.local/share}/venvs/${1:-venv}"
+ local requirements_path="${XDG_DATA_HOME:-${HOME}/.local/share}/venvs"
+
+ # Check if the environment already exists
+ # Create the virtual environment
+ echo "Creating new virtual environment in '$env_dir'..."
+ python3 -m venv $env_dir
+
+ # Activate the virtual environment
+ source $env_dir/bin/activate
+
+ # Optional: Install any default packages
+ pip3 install --upgrade pip >/dev/null 2>&1
+
+ if [ -f "$requirements_path/default-requirements.txt" ]; then
+ echo "Installing packages from '$requirements_path/default-requirements.txt'..."
+ pip3 install -r "$requirements_path/default-requirements.txt" >/dev/null 2>&1
+ fi
+
+ if [ -f "$requirements_path/captured-requirements.txt" ]; then
+ echo "Installing packages from '$requirements_path/captured-requirements.txt'..."
+ pip3 install -r "$requirements_path/captured-requirements.txt" >/dev/null 2>&1
+ fi
+
+ echo "Virtual environment '${1:-venv}' created and activated!"
+}
+
+# activate or switch venvs
+alias actv=active_venv
+function active_venv() {
+ local venv="$1"
+ if [[ -z "$venv" ]]; then
+ venv=$(find "$XDG_DATA_HOME/venvs" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | fzf)
+ fi
+ source "$XDG_DATA_HOME/venvs/$venv/bin/activate"
+ python -m ensurepip --upgrade >/dev/null 2>&1
+ python -m pip install --upgrade pip >/dev/null 2>&1
+}
+
+# list venvs
+alias listv=list_venv
+function list_venv() {
+ local venvs_dir="${XDG_DATA_HOME:-${HOME}/.local/share}/venvs"
+ local venvs=("$venvs_dir"/*)
+
+ if [ ${#venvs[@]} -eq 0 ]; then
+ echo "No venvs"
+ return 0
+ fi
+
+ echo "venvs list:"
+ for venv in "${venvs[@]}"; do
+ if [ -d "$venv" ]; then
+ echo " 󰢔 $(basename "$venv")"
+ fi
+ done
+}
+
+# deactivate venv
+alias deactv=deactive_venv
+function deactive_venv() {
+ if [[ "$VIRTUAL_ENV" != "" ]]; then
+ if [[ ! -f "${XDG_DATA_HOME:-${HOME}/.local/share}/venvs/requirements.txt" ]]; then
+ pip3 freeze > "${XDG_DATA_HOME:-${HOME}/.local/share}/venvs/captured-requirements.txt"
+ fi
+ deactivate
+ echo "Virtual environment deactivated and all installed packages captured"
+ else
+ echo "No virtual environment is active."
+ fi
+}
+
+# delete venv
+alias delv=delete_venv
+function delete_venv() {
+ local venv="$1"
+ local env_dir="${XDG_DATA_HOME:-${HOME}/.local/share}/venvs"
+
+ if [[ -z $venv ]]; then
+ local options=($(find "$env_dir" -maxdepth 1 -mindepth 1 -type d -exec basename {} \;))
+ options+=("Delete All")
+
+ # Prompt user to select virtual environments or choose to delete all
+ local selected_envs=$(printf "%s\n" "${options[@]}" | fzf --cycle --prompt="venvs  " --height=~50% --layout=reverse --border --multi --exit-0)
+
+ if [[ -z $selected_envs ]]; then
+ echo "No venvs selected"
+ return 0
+ elif [[ $selected_envs == *"Delete All"* ]]; then
+ rm -rf "$env_dir"/*
+ echo "All venvs deleted"
+ else
+ # Loop through selected environments and delete each
+ local env
+ while IFS= read -r env; do
+ rm -rf "$env_dir/$env"
+ echo "$env deleted"
+ done <<< "$selected_envs"
+ fi
+ else
+ rm -rf "$env_dir/$venv"
+ echo "$venv deleted"
+ fi
+}
diff --git a/ar/.gitmodules b/ar/.gitmodules
new file mode 100644
index 0000000..478205f
--- /dev/null
+++ b/ar/.gitmodules
@@ -0,0 +1,3 @@
+[submodule ".config/mpv/script_modules/mpvSockets"]
+ path = .config/mpv/script_modules/mpvSockets
+ url = https://github.com/wis/mpvSockets.git
diff --git a/ar/.local/bin/arkenfox-auto-update b/ar/.local/bin/arkenfox-auto-update
new file mode 100755
index 0000000..8e1ea7d
--- /dev/null
+++ b/ar/.local/bin/arkenfox-auto-update
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# A wrapper for the arkenfox-updater that runs it on all pre-existing Arkenfox
+# user.js files on the machine.
+
+# On installation of LARBS, this file is copied to /usr/local/lib/ where it is
+# run by a pacman hook set up. The user should not have to run this manually.
+
+# Search for all Firefox and Librewolf profiles using Arkenfox.
+profiles="$(grep -sH "arkenfox user.js" \
+ /home/*/.librewolf/*.default/user.js \
+ /home/*/.mozilla/firefox/*.default/user.js)"
+
+IFS='
+'
+
+# Update each found profile.
+for profile in $profiles; do
+ userjs=${profile%%/user.js*}
+ user=$(stat -c '%U' "$userjs") || continue
+
+ su -l "$user" -c "arkenfox-updater -c -p $userjs -s"
+done
+
+# RTMIN: pkill
+# PROJECT: pkill
diff --git a/ar/.local/bin/bash-preexec b/ar/.local/bin/bash-preexec
new file mode 100755
index 0000000..7222568
--- /dev/null
+++ b/ar/.local/bin/bash-preexec
@@ -0,0 +1,380 @@
+#!/bin/bash
+
+# bash-preexec.sh -- Bash support for ZSH-like 'preexec' and 'precmd' functions.
+# https://github.com/rcaloras/bash-preexec
+#
+#
+# 'preexec' functions are executed before each interactive command is
+# executed, with the interactive command as its argument. The 'precmd'
+# function is executed before each prompt is displayed.
+#
+# Author: Ryan Caloras (ryan@bashhub.com)
+# Forked from Original Author: Glyph Lefkowitz
+#
+# V0.5.0
+#
+
+# General Usage:
+#
+# 1. Source this file at the end of your bash profile so as not to interfere
+# with anything else that's using PROMPT_COMMAND.
+#
+# 2. Add any precmd or preexec functions by appending them to their arrays:
+# e.g.
+# precmd_functions+=(my_precmd_function)
+# precmd_functions+=(some_other_precmd_function)
+#
+# preexec_functions+=(my_preexec_function)
+#
+# 3. Consider changing anything using the DEBUG trap or PROMPT_COMMAND
+# to use preexec and precmd instead. Preexisting usages will be
+# preserved, but doing so manually may be less surprising.
+#
+# Note: This module requires two Bash features which you must not otherwise be
+# using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. If you override
+# either of these after bash-preexec has been installed it will most likely break.
+
+# Tell shellcheck what kind of file this is.
+# shellcheck shell=bash
+
+# Make sure this is bash that's running and return otherwise.
+# Use POSIX syntax for this line:
+if [ -z "${BASH_VERSION-}" ]; then
+ return 1
+fi
+
+# We only support Bash 3.1+.
+# Note: BASH_VERSINFO is first available in Bash-2.0.
+if [[ -z "${BASH_VERSINFO-}" ]] || ((BASH_VERSINFO[0] < 3 || (BASH_VERSINFO[0] == 3 && BASH_VERSINFO[1] < 1))); then
+ return 1
+fi
+
+# Avoid duplicate inclusion
+if [[ -n "${bash_preexec_imported:-}" || -n "${__bp_imported:-}" ]]; then
+ return 0
+fi
+bash_preexec_imported="defined"
+
+# WARNING: This variable is no longer used and should not be relied upon.
+# Use ${bash_preexec_imported} instead.
+# shellcheck disable=SC2034
+__bp_imported="${bash_preexec_imported}"
+
+# Should be available to each precmd and preexec
+# functions, should they want it. $? and $_ are available as $? and $_, but
+# $PIPESTATUS is available only in a copy, $BP_PIPESTATUS.
+# TODO: Figure out how to restore PIPESTATUS before each precmd or preexec
+# function.
+__bp_last_ret_value="$?"
+BP_PIPESTATUS=("${PIPESTATUS[@]}")
+__bp_last_argument_prev_command="$_"
+
+__bp_inside_precmd=0
+__bp_inside_preexec=0
+
+# Initial PROMPT_COMMAND string that is removed from PROMPT_COMMAND post __bp_install
+__bp_install_string=$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_install'
+
+# Fails if any of the given variables are readonly
+# Reference https://stackoverflow.com/a/4441178
+__bp_require_not_readonly() {
+ local var
+ for var; do
+ if ! (unset "$var" 2>/dev/null); then
+ echo "bash-preexec requires write access to ${var}" >&2
+ return 1
+ fi
+ done
+}
+
+# Remove ignorespace and or replace ignoreboth from HISTCONTROL
+# so we can accurately invoke preexec with a command from our
+# history even if it starts with a space.
+__bp_adjust_histcontrol() {
+ local histcontrol
+ histcontrol="${HISTCONTROL:-}"
+ histcontrol="${histcontrol//ignorespace/}"
+ # Replace ignoreboth with ignoredups
+ if [[ "$histcontrol" == *"ignoreboth"* ]]; then
+ histcontrol="ignoredups:${histcontrol//ignoreboth/}"
+ fi
+ export HISTCONTROL="$histcontrol"
+}
+
+# This variable describes whether we are currently in "interactive mode";
+# i.e. whether this shell has just executed a prompt and is waiting for user
+# input. It documents whether the current command invoked by the trace hook is
+# run interactively by the user; it's set immediately after the prompt hook,
+# and unset as soon as the trace hook is run.
+__bp_preexec_interactive_mode=""
+
+# These arrays are used to add functions to be run before, or after, prompts.
+declare -a precmd_functions
+declare -a preexec_functions
+
+# Trims leading and trailing whitespace from $2 and writes it to the variable
+# name passed as $1
+__bp_trim_whitespace() {
+ local var=${1:?} text=${2:-}
+ text="${text#"${text%%[![:space:]]*}"}" # remove leading whitespace characters
+ text="${text%"${text##*[![:space:]]}"}" # remove trailing whitespace characters
+ printf -v "$var" '%s' "$text"
+}
+
+# Trims whitespace and removes any leading or trailing semicolons from $2 and
+# writes the resulting string to the variable name passed as $1. Used for
+# manipulating substrings in PROMPT_COMMAND
+__bp_sanitize_string() {
+ local var=${1:?} text=${2:-} sanitized
+ __bp_trim_whitespace sanitized "$text"
+ sanitized=${sanitized%;}
+ sanitized=${sanitized#;}
+ __bp_trim_whitespace sanitized "$sanitized"
+ printf -v "$var" '%s' "$sanitized"
+}
+
+# This function is installed as part of the PROMPT_COMMAND;
+# It sets a variable to indicate that the prompt was just displayed,
+# to allow the DEBUG trap to know that the next command is likely interactive.
+__bp_interactive_mode() {
+ __bp_preexec_interactive_mode="on"
+}
+
+# This function is installed as part of the PROMPT_COMMAND.
+# It will invoke any functions defined in the precmd_functions array.
+__bp_precmd_invoke_cmd() {
+ # Save the returned value from our last command, and from each process in
+ # its pipeline. Note: this MUST be the first thing done in this function.
+ # BP_PIPESTATUS may be unused, ignore
+ # shellcheck disable=SC2034
+
+ __bp_last_ret_value="$?" BP_PIPESTATUS=("${PIPESTATUS[@]}")
+
+ # Don't invoke precmds if we are inside an execution of an "original
+ # prompt command" by another precmd execution loop. This avoids infinite
+ # recursion.
+ if ((__bp_inside_precmd > 0)); then
+ return
+ fi
+ local __bp_inside_precmd=1
+
+ # Invoke every function defined in our function array.
+ local precmd_function
+ for precmd_function in "${precmd_functions[@]}"; do
+
+ # Only execute this function if it actually exists.
+ # Test existence of functions with: declare -[Ff]
+ if type -t "$precmd_function" 1>/dev/null; then
+ __bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command"
+ # Quote our function invocation to prevent issues with IFS
+ "$precmd_function"
+ fi
+ done
+
+ __bp_set_ret_value "$__bp_last_ret_value"
+}
+
+# Sets a return value in $?. We may want to get access to the $? variable in our
+# precmd functions. This is available for instance in zsh. We can simulate it in bash
+# by setting the value here.
+__bp_set_ret_value() {
+ return ${1:+"$1"}
+}
+
+__bp_in_prompt_command() {
+
+ local prompt_command_array IFS=$'\n;'
+ read -rd '' -a prompt_command_array <<<"${PROMPT_COMMAND[*]:-}"
+
+ local trimmed_arg
+ __bp_trim_whitespace trimmed_arg "${1:-}"
+
+ local command trimmed_command
+ for command in "${prompt_command_array[@]:-}"; do
+ __bp_trim_whitespace trimmed_command "$command"
+ if [[ "$trimmed_command" == "$trimmed_arg" ]]; then
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+# This function is installed as the DEBUG trap. It is invoked before each
+# interactive prompt display. Its purpose is to inspect the current
+# environment to attempt to detect if the current command is being invoked
+# interactively, and invoke 'preexec' if so.
+__bp_preexec_invoke_exec() {
+
+ # Save the contents of $_ so that it can be restored later on.
+ # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702
+ __bp_last_argument_prev_command="${1:-}"
+ # Don't invoke preexecs if we are inside of another preexec.
+ if ((__bp_inside_preexec > 0)); then
+ return
+ fi
+ local __bp_inside_preexec=1
+
+ # Checks if the file descriptor is not standard out (i.e. '1')
+ # __bp_delay_install checks if we're in test. Needed for bats to run.
+ # Prevents preexec from being invoked for functions in PS1
+ if [[ ! -t 1 && -z "${__bp_delay_install:-}" ]]; then
+ return
+ fi
+
+ if [[ -n "${COMP_POINT:-}" || -n "${READLINE_POINT:-}" ]]; then
+ # We're in the middle of a completer or a keybinding set up by "bind
+ # -x". This obviously can't be an interactively issued command.
+ return
+ fi
+ if [[ -z "${__bp_preexec_interactive_mode:-}" ]]; then
+ # We're doing something related to displaying the prompt. Let the
+ # prompt set the title instead of me.
+ return
+ else
+ # If we're in a subshell, then the prompt won't be re-displayed to put
+ # us back into interactive mode, so let's not set the variable back.
+ # In other words, if you have a subshell like
+ # (sleep 1; sleep 2)
+ # You want to see the 'sleep 2' as a set_command_title as well.
+ if [[ 0 -eq "${BASH_SUBSHELL:-}" ]]; then
+ __bp_preexec_interactive_mode=""
+ fi
+ fi
+
+ if __bp_in_prompt_command "${BASH_COMMAND:-}"; then
+ # If we're executing something inside our prompt_command then we don't
+ # want to call preexec. Bash prior to 3.1 can't detect this at all :/
+ __bp_preexec_interactive_mode=""
+ return
+ fi
+
+ local this_command
+ this_command=$(
+ export LC_ALL=C
+ HISTTIMEFORMAT='' builtin history 1 | sed '1 s/^ *[0-9][0-9]*[* ] //'
+ )
+
+ # Sanity check to make sure we have something to invoke our function with.
+ if [[ -z "$this_command" ]]; then
+ return
+ fi
+
+ # Invoke every function defined in our function array.
+ local preexec_function
+ local preexec_function_ret_value
+ local preexec_ret_value=0
+ for preexec_function in "${preexec_functions[@]:-}"; do
+
+ # Only execute each function if it actually exists.
+ # Test existence of function with: declare -[fF]
+ if type -t "$preexec_function" 1>/dev/null; then
+ __bp_set_ret_value "${__bp_last_ret_value:-}"
+ # Quote our function invocation to prevent issues with IFS
+ "$preexec_function" "$this_command"
+ preexec_function_ret_value="$?"
+ if [[ "$preexec_function_ret_value" != 0 ]]; then
+ preexec_ret_value="$preexec_function_ret_value"
+ fi
+ fi
+ done
+
+ # Restore the last argument of the last executed command, and set the return
+ # value of the DEBUG trap to be the return code of the last preexec function
+ # to return an error.
+ # If `extdebug` is enabled a non-zero return value from any preexec function
+ # will cause the user's command not to execute.
+ # Run `shopt -s extdebug` to enable
+ __bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command"
+}
+
+__bp_install() {
+ # Exit if we already have this installed.
+ if [[ "${PROMPT_COMMAND[*]:-}" == *"__bp_precmd_invoke_cmd"* ]]; then
+ return 1
+ fi
+
+ trap '__bp_preexec_invoke_exec "$_"' DEBUG
+
+ # Preserve any prior DEBUG trap as a preexec function
+ local prior_trap
+ # we can't easily do this with variable expansion. Leaving as sed command.
+ # shellcheck disable=SC2001
+ prior_trap=$(sed "s/[^']*'\(.*\)'[^']*/\1/" <<<"${__bp_trap_string:-}")
+ unset __bp_trap_string
+ if [[ -n "$prior_trap" ]]; then
+ eval '__bp_original_debug_trap() {
+ '"$prior_trap"'
+ }'
+ preexec_functions+=(__bp_original_debug_trap)
+ fi
+
+ # Adjust our HISTCONTROL Variable if needed.
+ __bp_adjust_histcontrol
+
+ # Issue #25. Setting debug trap for subshells causes sessions to exit for
+ # backgrounded subshell commands (e.g. (pwd)& ). Believe this is a bug in Bash.
+ #
+ # Disabling this by default. It can be enabled by setting this variable.
+ if [[ -n "${__bp_enable_subshells:-}" ]]; then
+
+ # Set so debug trap will work be invoked in subshells.
+ set -o functrace >/dev/null 2>&1
+ shopt -s extdebug >/dev/null 2>&1
+ fi
+
+ local existing_prompt_command
+ # Remove setting our trap install string and sanitize the existing prompt command string
+ existing_prompt_command="${PROMPT_COMMAND:-}"
+ # Edge case of appending to PROMPT_COMMAND
+ existing_prompt_command="${existing_prompt_command//$__bp_install_string/:}" # no-op
+ existing_prompt_command="${existing_prompt_command//$'\n':$'\n'/$'\n'}" # remove known-token only
+ existing_prompt_command="${existing_prompt_command//$'\n':;/$'\n'}" # remove known-token only
+ __bp_sanitize_string existing_prompt_command "$existing_prompt_command"
+ if [[ "${existing_prompt_command:-:}" == ":" ]]; then
+ existing_prompt_command=
+ fi
+
+ # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've
+ # actually entered something.
+ PROMPT_COMMAND='__bp_precmd_invoke_cmd'
+ PROMPT_COMMAND+=${existing_prompt_command:+$'\n'$existing_prompt_command}
+ if ((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1))); then
+ PROMPT_COMMAND+=('__bp_interactive_mode')
+ else
+ # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND+=$'\n__bp_interactive_mode'
+ fi
+
+ # Add two functions to our arrays for convenience
+ # of definition.
+ precmd_functions+=(precmd)
+ preexec_functions+=(preexec)
+
+ # Invoke our two functions manually that were added to $PROMPT_COMMAND
+ __bp_precmd_invoke_cmd
+ __bp_interactive_mode
+}
+
+# Sets an installation string as part of our PROMPT_COMMAND to install
+# after our session has started. This allows bash-preexec to be included
+# at any point in our bash profile.
+__bp_install_after_session_init() {
+ # bash-preexec needs to modify these variables in order to work correctly
+ # if it can't, just stop the installation
+ __bp_require_not_readonly PROMPT_COMMAND HISTCONTROL HISTTIMEFORMAT || return
+
+ local sanitized_prompt_command
+ __bp_sanitize_string sanitized_prompt_command "${PROMPT_COMMAND:-}"
+ if [[ -n "$sanitized_prompt_command" ]]; then
+ # shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND=${sanitized_prompt_command}$'\n'
+ fi
+ # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND+=${__bp_install_string}
+}
+
+# Run our install so long as we're not delaying it.
+if [[ -z "${__bp_delay_install:-}" ]]; then
+ __bp_install_after_session_init
+fi
diff --git a/ar/.local/bin/bookmarks b/ar/.local/bin/bookmarks
new file mode 100755
index 0000000..5e81c07
--- /dev/null
+++ b/ar/.local/bin/bookmarks
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+usage() {
+ echo "Open bookmarks, URLs, or browser history in a program."
+ echo ""
+ echo "Usage: ${0##*/} [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " -h : Show this message"
+ echo " -b : Open a browser bookmark"
+ echo " -c : Copy a URL from snippets/URLs to the clipboard"
+ echo " -o : Get a URL from snippets/URLs and open it in a new browser window"
+ echo " -p : Get a URL from snippets/URLs and open it in a private browser window"
+ echo " -s : Open browser history"
+ echo " -t : Get a URL from snippets/URLs and type it using xdotool"
+ echo " -v : Open a browser bookmark in private browser window"
+ echo ""
+ echo "Programs:"
+ echo " browser : System default browser"
+ echo " links : A text WWW browser, similar to lynx"
+ echo " lynx : A text browser for World Wide Web"
+ echo ""
+ echo "Examples:"
+ echo " ${0##*/} -b # Opens a browser bookmark in a program"
+ echo " ${0##*/} -c # Copies a URL from snippets/URLs to the clipboard"
+ echo " ${0##*/} -o # Opens a URL from snippets/URLs in a new browser window"
+ echo " ${0##*/} -p # Opens a URL in a private browser window"
+ echo " ${0##*/} -s # Opens browser history in a program"
+ echo " ${0##*/} -v # Opens browser boomark in private browser window"
+}
+
+open() {
+ available_tools=""
+ command -v xdg-open 2>/dev/null | grep -v "alias" -q && available_tools="$available_tools xdg-open"
+ command -v open 2>/dev/null | grep -v "alias" -q && available_tools="$available_tools open"
+ command -v links 2>/dev/null | grep -v "alias" -q && available_tools="$available_tools links"
+ command -v lynx 2>/dev/null | grep -v "alias" -q && available_tools="$available_tools lynx"
+ available_tools=$(printf "%s" "$available_tools" | awk '{$1=$1; print}' | tr ' ' '\n')
+ if [ -z "$available_tools" ]; then
+ printf "No browser found\n" >&2
+ exit 1
+ fi
+
+ opentool=$(printf "%s\n" "$available_tools" | dmenu -i -p "Choose an open tool:")
+
+ # Set the selected tool to the variable 'open'
+ case "$opentool" in
+ xdg-open) xdg-open "$1" ;;
+ open)
+ case "$(uname -s)" in
+ Darwin)
+ open "$1"
+ ;;
+ *)
+ xdg-open "$1"
+ ;;
+ esac
+ ;;
+ links) setsid -f "$TERMINAL" -e links "$1" ;;
+ lynx) setsid -f "$TERMINAL" -e lynx "$1" ;;
+ *) echo "Invalid selection" >&2 && exit 1 ;;
+ esac
+}
+
+browsercheck() {
+ if pidof "$BROWSER" >/dev/null; then
+ notify-send "❌ Failed to $1 from '$BROWSER'." "'$BROWSER' is locked. Check if '$BROWSER' is running."
+ exit 1
+ fi
+}
+
+openinbrowser() {
+ # Extract only the default part of the profile name
+ case $BROWSER in
+ firefox)
+ profiles_ini_path="$HOME/.mozilla/firefox/profiles.ini"
+ profile=$(awk '/\[Install/ {found=1} found && /^Default=/ {split($0, arr, "."); print arr[2]; exit}' "$profiles_ini_path")
+ profile_dir=$(find ~/.mozilla/firefox -type d -name "*.$profile*" | head -n 1)
+ ;;
+ librewolf)
+ profiles_ini_path="$HOME/.librewolf/profiles.ini"
+ profile=$(awk '/\[Install/ {found=1} found && /^Default=/ {split($0, arr, "."); print arr[2]; exit}' "$profiles_ini_path")
+ profile_dir=$(find ~/.librewolf -type d -name "*.$profile*" | head -n 1)
+ ;;
+ *) echo "Default browser path is needed." && exit ;;
+ esac
+
+ db_path="$profile_dir/places.sqlite"
+ tmp_dir="${XDG_CACHE_HOME:-$HOME/.cache}/mozilla/firefox/$USER.$profile"
+ tmp_file="$tmp_dir/$1.sqlite"
+ mkdir -p "$tmp_dir"
+ cp -f "$db_path" "$tmp_file"
+
+ type dmenu >/dev/null 2>&1 &&
+ selection="dmenu -i -l 20 -p \"Choose a $1 to open:\"" ||
+ selection="fzf-tmux --reverse --cycle --ansi --delimiter='|' --with-nth=1..-2"
+
+ case "$1" in
+ *bookmark*)
+ sqlite_query="
+ SELECT b.title || ' | ' || p.url AS bookmark
+ FROM moz_bookmarks b
+ JOIN moz_places p ON b.fk = p.id
+ WHERE b.type = 1 AND p.url LIKE 'http%' AND b.title NOT NULL
+ ORDER BY b.dateAdded DESC;
+ "
+ ;;
+ *history*)
+ cols=$((${COLUMNS:-90} / 3))
+ sqlite_query="
+ SELECT substr(p.title, 1, $cols) || ' | ' || p.url
+ FROM moz_places p
+ JOIN moz_historyvisits hv ON hv.place_id = p.id
+ ORDER BY hv.visit_date DESC LIMIT 100;
+ "
+ ;;
+ esac
+ choice=$(sqlite3 "$tmp_file" "$sqlite_query" | eval "$selection" | cut -d'|' -f2 | sed 's|.*\(https://\)|\1|' | xargs)
+ if [ -n "$choice" ]; then
+ if echo "$1" | grep -q "private"; then
+ "$BROWSER" --private-window "$choice"
+ else
+ open "$choice"
+ fi
+ else
+ exit
+ fi
+}
+
+geturls() {
+ [ -f ~/.local/share/thesiah/urls ] &&
+ URLS=$(cat ~/.local/share/thesiah/snippets ~/.local/share/thesiah/urls) ||
+ URLS=$(cat ~/.local/share/thesiah/snippets)
+ CHOICE=$(echo "$URLS" | grep -v -e '^#' -e '^$' | awk -F'"' '{print $2}' | dmenu -i -l 50 -p "Choose a URL $1:")
+ [ -z "$CHOICE" ] && exit
+ URL=$(echo "$URLS" | grep -v -e '^#' -e '^$' | grep "\"$CHOICE\"" | awk '{print $1}')
+}
+
+copytoclipboard() {
+ if command -v xclip >/dev/null 2>&1; then
+ printf "%s" "$URL" | xclip -selection clipboard
+ elif command -v xsel >/dev/null 2>&1; then
+ printf "%s" "$URL" | xsel --clipboard --input
+ else
+ echo "Clipboard utility not found. Install xclip or xsel." >&2
+ exit 1
+ fi
+ notify-send "'$CHOICE' copied in clipbaord" "$URL"
+}
+
+[ $# -eq 0 ] && usage && exit 1
+
+while getopts "bchopstv" opt; do
+ case $opt in
+ b) browsercheck "open bookmark" && openinbrowser "bookmark" ;;
+ c) geturls "to copy" && copytoclipboard ;;
+ o) geturls "to open in $BROWSER" && "$BROWSER" --new-window "$URL" ;;
+ p) geturls "to open in private $BROWSER" && "$BROWSER" --private-window "$URL" ;;
+ s) browsercheck "open history" && openinbrowser "history" ;;
+ t) geturls "to type under cursor" && xdotool type "$URL" ;;
+ v) browsercheck "open bookmark" && openinbrowser "private bookmark" ;;
+ h | *) usage && exit 0 ;;
+ esac
+done
diff --git a/ar/.local/bin/booksplit b/ar/.local/bin/booksplit
new file mode 100755
index 0000000..bdf637a
--- /dev/null
+++ b/ar/.local/bin/booksplit
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# Requires ffmpeg
+
+[ ! -f "$2" ] && printf "The first file should be the audio, the second should be the timecodes.\\n" && exit
+
+echo "Enter the album/book title:"
+read -r booktitle
+echo "Enter the artist/author:"
+read -r author
+echo "Enter the publication year:"
+read -r year
+
+inputaudio="$1"
+ext="${1##*.}"
+
+# Get a safe file name from the book.
+escbook="$(echo "$booktitle" | iconv -c -f UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g")"
+
+! mkdir -p "$escbook" &&
+ echo "Do you have write access in this directory?" &&
+ exit 1
+
+# Get the total number of tracks from the number of lines.
+total="$(wc -l <"$2")"
+
+cmd="ffmpeg -i \"$inputaudio\" -nostdin -y"
+
+while read -r x; do
+ end="$(echo "$x" | cut -d' ' -f1)"
+ file="$escbook/$(printf "%.2d" "$track")-$esctitle.$ext"
+ if [ -n "$start" ]; then
+ cmd="$cmd -metadata artist=\"$author\" -metadata title=\"$title\" -metadata album=\"$booktitle\" -metadata year=\"$year\" -metadata track=\"$track\" -metadata total=\"$total\" -ss \"$start\" -to \"$end\" -vn -c:a copy \"$file\" "
+ fi
+ title="$(echo "$x" | cut -d' ' -f2-)"
+ esctitle="$(echo "$title" | iconv -c -f UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g")"
+ track="$((track + 1))"
+ start="$end"
+done <"$2"
+
+# Last track must be added out of the loop.
+file="$escbook/$(printf "%.2d" "$track")-$esctitle.$ext"
+cmd="$cmd -metadata artist=\"$author\" -metadata title=\"$title\" -metadata album=\"$booktitle\" -metadata year=\"$year\" -metadata track=\"$track\" -ss \"$start\" -vn -c copy \"$file\""
+
+eval "$cmd"
diff --git a/ar/.local/bin/browse b/ar/.local/bin/browse
new file mode 100755
index 0000000..56c36d9
--- /dev/null
+++ b/ar/.local/bin/browse
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+# Usage message
+usage() {
+ echo "Searches query in a terminal or browser"
+ echo ""
+ echo "Usage: ${0##*/} [-d | --ddgr | ddgr] [-h | --help | help] [<search_engine>] <search_query>"
+ echo ""
+ echo "Options:"
+ echo " -d, --ddgr, ddgr : Use ddgr to search and open the result in a terminal"
+ echo " -h, --help, help : Display this help message"
+ echo " <search_engine> : (Optional) Search engine to use (google, bing, yahoo, duckduckgo, youtube)"
+ echo " <search_query> : The search term or query to use"
+}
+
+# Set default values
+SEARCH_TOOL="web"
+SEARCH_ENGINE="searx"
+
+# Determine the open command based on the operating system
+case "$(uname -s)" in
+Darwin)
+ open_cmd='open'
+ ;;
+*)
+ open_cmd='xdg-open'
+ ;;
+esac
+
+# Check the first argument for flags or help using case
+case "$1" in
+-d | --ddgr | ddgr)
+ # Check if ddgr is installed (only needed for ddgr options)
+ if ! command -v ddgr >/dev/null 2>&1; then
+ echo "Error: ddgr is not installed." >&2
+ exit 1
+ fi
+ SEARCH_TOOL="ddgr"
+ shift # Remove this argument from the list
+ ;;
+-h | --help | help)
+ usage && exit 0
+ ;;
+bing | duckduckgo | google | yahoo | youtube)
+ SEARCH_ENGINE="$1"
+ shift # Remove the search engine from the list
+ ;;
+esac
+
+# Store the remaining arguments as the search query
+SEARCH_QUERY="$*"
+
+# Ensure a search query is provided; if not, show usage
+[ -z "$SEARCH_QUERY" ] && usage && exit 1
+
+# Execute the corresponding search tool using case
+case $SEARCH_TOOL in
+ddgr)
+ # Run DuckDuckGo search in the terminal
+ setsid -f "$TERMINAL" -e ddgr "$SEARCH_QUERY"
+ ;;
+web)
+ # Construct the URL based on the search engine
+ case "$SEARCH_ENGINE" in
+ bing | google | yahoo | youtube)
+ base_url="https://www.${SEARCH_ENGINE}.com/search?q="
+ ;;
+ duckduckgo)
+ base_url="https://duckduckgo.com/?q="
+ ;;
+ searx | *)
+ base_url="https://www.searx.thesiah.xyz/search?q="
+ ;;
+ esac
+
+ # Encode the search query
+ SEARCH_QUERY_ENCODED=$(echo "$SEARCH_QUERY" | sed 's/ /+/g')
+
+ # Open the search URL in the default browser
+ $open_cmd "${base_url}${SEARCH_QUERY_ENCODED}"
+ ;;
+*)
+ usage && exit 1
+ ;;
+esac
diff --git a/ar/.local/bin/browserprofile b/ar/.local/bin/browserprofile
new file mode 100755
index 0000000..ca49f02
--- /dev/null
+++ b/ar/.local/bin/browserprofile
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+# Define the profile paths
+WORK_PROFILE="si.work"
+HOME_PROFILE="si.default"
+
+usage() {
+ echo "Update the default profile in profiles.ini for Firefox or Librewolf."
+ echo ""
+ echo "Usage: ${0##*/} <browser> [<profile_type>]"
+ echo ""
+ echo "Options:"
+ echo " -h : Show this help message."
+ echo " <browser> : The browser to configure."
+ echo " Accepted values:"
+ echo " -f/firefox"
+ echo " -l/librewolf"
+ echo " <profile_type> : (Optional) If not specified, the default profile will be used."
+ echo " Accepted values:"
+ echo " work: Sets the work profile ($WORK_PROFILE)"
+ echo " default: Sets the home profile ($HOME_PROFILE)"
+ echo ""
+ echo "Examples:"
+ echo " ${0##*/} -f -w # Set the work profile for Firefox"
+ echo " ${0##*/} -l -d # Set the default profile for Librewolf"
+ echo " ${0##*/} -f -d # Set the default profile for Firefox"
+}
+
+update_profiles_ini() {
+ case "$1" in
+ -f | --firefox | firefox) profiles_ini_path="$HOME/.mozilla/firefox/profiles.ini" ;;
+ -l | --librewolf | librewolf) profiles_ini_path="$HOME/.librewolf/profiles.ini" ;;
+ *)
+ echo "Invalid browser type. Please use 'firefox' or 'librewolf'."
+ return 1
+ ;;
+ esac
+
+ profile_to_set=$2
+
+ # Backup current profiles.ini
+ cp "$profiles_ini_path" "$profiles_ini_path.bak"
+
+ # Update the profiles.ini
+ awk -v profile="$profile_to_set" '
+ /^\[Install/ {
+ print
+ found=1
+ next
+ }
+ found && /^Default=/ {
+ sub(/=.*/, "=" profile)
+ print
+ next
+ }
+ {
+ print
+ }' "$profiles_ini_path" >"$profiles_ini_path.tmp" && mv "$profiles_ini_path.tmp" "$profiles_ini_path"
+
+ echo "Updated profiles.ini to use profile: $profile_to_set"
+}
+
+# Main function
+update_profile() {
+ browser=$1
+ profile_type=$2
+
+ # Check if a browser is provided
+ if [ -z "$browser" ]; then
+ usage && exit 1
+ fi
+
+ # Convert profile_type to lowercase for case-insensitive comparison
+ if [ -n "$profile_type" ]; then
+ profile_type=$(echo "$profile_type" | tr '[:upper:]' '[:lower:]')
+ else
+ # Set to "default" if profile_type is not given
+ profile_type="default"
+ fi
+
+ # Set the profile based on the input
+ case "$profile_type" in
+ -w | --work | work)
+ update_profiles_ini "$browser" "$WORK_PROFILE"
+ ;;
+ -d | --default | default)
+ update_profiles_ini "$browser" "$HOME_PROFILE"
+ ;;
+ *)
+ echo "Invalid profile type. Please use 'work' or 'default'."
+ return 1
+ ;;
+ esac
+}
+
+# Check for help flag
+if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
+ usage && exit 0
+fi
+
+# Execute the main function with arguments passed to the script
+update_profile "$1" "$2"
diff --git a/ar/.local/bin/clonerepo b/ar/.local/bin/clonerepo
new file mode 100755
index 0000000..cc1e23f
--- /dev/null
+++ b/ar/.local/bin/clonerepo
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+usage() {
+ echo "Clone a git repo in XDG_PUBLICSHARE_DIR if it's set or ~/Public"
+ echo ""
+ echo "Usage: ${0##*/} [-b <branch>] [-d <destination>] [-n <name>] <url>"
+ echo ""
+ echo "Options:"
+ echo " -h : Show this help message"
+ echo " -b <branch> : Specify the branch to clone"
+ echo " -d <destination> : Specify the destination of directory"
+ echo " -n <name> : Specify the directory name to clone into"
+ echo ""
+ echo "Example:"
+ echo " ${0##*/} -b master <url> # Clone master branch"
+ echo " ${0##*/} -d ~/.local/bin <url> # Clone the url into ~/.local/bin"
+ echo " ${0##*/} -n myrepo <url> # Clone url named with myrepo"
+ exit 1
+}
+
+# Default values
+path="${XDG_PUBLICSHARE_DIR:-$HOME/Public}"
+
+# Parse options
+while getopts ":b:d:n:" opt; do
+ case $opt in
+ b) branch="$OPTARG" ;;
+ d) path="$OPTARG" ;;
+ n) dirname="$OPTARG" ;;
+ *) usage ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+[ -z "$1" ] && usage
+
+repo="$1"
+
+# Validate the URL (supports HTTPS and SSH Git URLs)
+if ! echo "$repo" | grep -Eq '^(https://github\.com/[^/]+/[^/]+(\.git)?|git@github\.com:[^/]+/[^/]+(\.git)?)$'; then
+ echo "Error: Invalid Git URL."
+ exit 1
+fi
+
+# Extract the base URL for the repository (removes any trailing .git)
+url="$(echo "$repo" | grep -Eo 'https://github.com/[^/]+/[^/]+' | sed 's/\.git$//')"
+
+# Determine the directory name for cloning
+if [ -n "$dirname" ]; then
+ dest="$path/$dirname"
+else
+ dest="$path/$(basename "$url")"
+fi
+
+# Determine the clone command
+if [ -n "$branch" ]; then
+ git clone --branch "$branch" "$url" "$dest"
+else
+ git clone "$url" "$dest"
+fi
diff --git a/ar/.local/bin/compiler b/ar/.local/bin/compiler
new file mode 100755
index 0000000..32004c2
--- /dev/null
+++ b/ar/.local/bin/compiler
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+# This script will compile or run another finishing operation on a document. I
+# have this script run via Vim.
+# # tex files: Compiles to pdf, including bibliography if necessary
+# md files: Compiles to pdf via pandoc
+# rmd files: Compiles via R Markdown
+# c files: Compiles via whatever compiler is set to cc. Usually gcc.
+# Use make if Makefile exists.
+# py files: runs via python command
+# go files: compiles and runs with "go run"
+# config.h files: (For suckless utils) recompiles and installs program.
+# all others: run `sent` to show a presentation
+
+file=$(readlink -f "$1")
+dir=$(dirname "$file")
+base="${file%.*}"
+
+cd "$dir" || exit
+
+Ifinstalled() {
+ command -v "$1" >/dev/null || { notify-send "📦 <b>$1</b> must be installed for this function." && exit 1; }
+}
+
+textype() {
+ command="pdflatex"
+ errorfmt="-file-line-error"
+ # ( sed 5q "$file" | grep -i -q 'xelatex' ) && command="xelatex"
+ secdir="$(dirname "$dir")"
+ cd "$secdir"
+ if [ -f "${secdir}/Notes.tex" ]; then
+ echo "${secdir}/Notes.tex"
+ $command $errorfmt --output-directory="$secdir" "${secdir}/Notes.tex"
+ exit
+ fi
+ $command $errorfmt --output-directory="$dir" "$base"
+ grep -i addbibresource "$file" >/dev/null &&
+ biber --input-directory "$dir" "$base" &&
+ $command $errorfmt --output-directory="$dir" "$base" &&
+ $command $errorfmt --output-directory="$dir" "$base"
+}
+
+pandoccmd() {
+ # Ifinstalled pdflatex && pandoc -V geometry:margin=4cm -f markdown-implicit_figures "$1" -o "${2}.pdf"
+ Ifinstalled groff && pandoc "${1}" -t ms --pdf-engine-opt=-p -o "${2}.pdf"
+}
+
+pandocorg() { pandoccmd "$file" "$base"; }
+
+compilec() {
+ if [ -f "${dir}/Makefile" ]; then
+ make
+ else
+ cc "$file" -o "$base" && "$base"
+ fi
+}
+
+case "$file" in
+*\.[0-9]) preconv "$file" | refer -PS -e | groff -mandoc -T pdf >"$base".pdf ;;
+*\.apl) apl -f "$file" ;;
+*\.c) compilec ;;
+*config.h) make && sudo make install ;;
+*\.cpp) g++ "$file" -o "$base" && "$base" ;;
+*\.cs) mcs "$file" && mono "$base".exe ;;
+*\.docx | *\.doc)
+ Ifinstalled libreoffice && lowriter --convert-to pdf "$file" && exit
+ Ifinstalled pandoc && pandoccmd "$file" "$base" && exit
+ ;;
+*\.dot | *\.gv) dot -Tsvg "$file" | convert svg:- "$base".eps ;;
+*\.h) compilec ;;
+*\.html) refreshbrowser ;;
+*\.fnl)
+ echo "fennel --compile '$file' > '$base'.lua"
+ fennel --compile "$file" >"$base".lua
+ ;;
+*\.go) go run "$file" ;;
+*\.java) javac "$file" && echo "${base##*/}" | xargs java ;;
+*\.js) node "$file" ;;
+*\.m) octave "$file" ;;
+*\.md) [ -x "$(command -v lowdown)" ] &&
+ lowdown --parse-no-intraemph "${file}" -Tms | groff -mpdfmark -ms -kept -T pdf >"${base}.pdf" ||
+ [ -x "$(command -v groffdown)" ] &&
+ groffdown -i "${file}" | groff -T pdf >"${base}.pdf" ||
+ pandoc -t ms --highlight-style="kate" -s -o "${base}.pdf" "${file}" ;;
+*\.me) groff -Gktes -b -w w -me -T ps "$file" | ps2pdf - >"$base".pdf ;;
+*\.mm) groff -Gktes -b -w w -mm -mpic -T ps "$file" | ps2pdf - >"$base".pdf ;;
+*\.mom) pdfroff -pktes -b -wall -mom -mpdfmark "$file" >"$base".pdf ;;
+*\.ms | *\.groff) preconv "$file" | groff -Tpdf -ktesp -G -ms >"$base".pdf ;;
+*\.org) Ifinstalled pandoc && pandocorg "$file" "$base" && exit ;;
+*\.present) groff -p -e -t -mm -mpresent "$file" | presentps -l | ps2pdf - >"$base".pdf ;;
+*\.ps) ps2pdf "$file" ;;
+*\.py) python "$file" ;;
+*\.[rR]md) Rscript -e "rmarkdown::render('$file', quiet=TRUE)" ;;
+*\.r) R -f "$file" ;;
+*\.rkt) racket "$file" ;;
+*\.rs) cargo build ;;
+*\.sass) sassc -a "$file" "$base".css ;;
+*\.scad) openscad -o "$base".stl "$file" ;;
+*\.sent) setsid -f sent "$file" 2>/dev/null & ;;
+*\.tex) textype "$file" ;;
+*\.tcl) tclsh "$file" ;;
+*\.vim*) vint "$file" ;;
+*) chmod +x "$file" && sed 1q "$file" | grep "^#!/" | sed "s/^#!//" | xargs -r -I % "$file" ;;
+esac
diff --git a/ar/.local/bin/concatvideo b/ar/.local/bin/concatvideo
new file mode 100755
index 0000000..93a2060
--- /dev/null
+++ b/ar/.local/bin/concatvideo
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+usage() {
+ echo "Combine multiple video files that share the same pattern and extension"
+ echo "into a single video file."
+ echo ""
+ echo "Usage: ${0##*/} <base filename>"
+ echo ""
+ echo "Arguments:"
+ echo " <base filename>: The name of one of the video files to use as a pattern."
+ echo " For example, if your files are named video_cut1.mp4,"
+ echo " video_cut2.mp4, you can provide video_cut1.mp4."
+ echo ""
+ echo "Example:"
+ echo " ${0##*/} video_cut.mp4"
+ echo " This will combine all files matching video_cut*.mp4 into a single file."
+ exit 1
+}
+
+# Check if the correct number of arguments are provided
+if [ $# -ne 1 ]; then
+ usage
+fi
+
+# Extract the pattern and extension from the input filename
+input_file="$1"
+pattern="${input_file%.*}" # This removes the extension
+extension="${input_file##*.}" # This extracts the extension
+
+# Find all video files matching the generated pattern and extension
+video_files=$(ls "${pattern}"*."${extension}" 2>/dev/null)
+
+# Check if any files are found
+if [ -z "$video_files" ]; then
+ echo "No video files found with the pattern '${pattern}*.${extension}'."
+ exit 1
+fi
+
+# Create a temporary file list for ffmpeg
+file_list=$(mktemp)
+
+# Populate the file list with the found video files, properly quoting each full path
+for video in "${pattern}"*."${extension}"; do
+ full_path=$(realpath "$video")
+ echo "file '$full_path'" >>"$file_list"
+done
+
+# Combine the videos into a single file using ffmpeg
+output_file="${pattern}_combine.${extension}"
+ffmpeg -f concat -safe 0 -i "$file_list" -c copy "$output_file" 2>/dev/null
+
+# Clean up the temporary file
+cat "$file_list"
+rm -f "$file_list"
+
+echo "All videos combined into '$output_file'."
diff --git a/ar/.local/bin/createsh b/ar/.local/bin/createsh
new file mode 100755
index 0000000..53c2a32
--- /dev/null
+++ b/ar/.local/bin/createsh
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+# Function to display help
+usage() {
+ echo "Generate a shell script in sh, bash, or zsh"
+ echo ""
+ echo "Usage: ${0##*/} [OPTION] [FILENAME]"
+ echo ""
+ echo "Options:"
+ echo " -h, --help : Show this help message."
+ echo " -b, --bash : Create a Bash script (bash)."
+ echo " -d, --dash : Create a POSIX-compliant shell script (sh)."
+ echo " -z, --zsh : Create a Zsh script (zsh)."
+ echo ""
+ echo "Example:"
+ echo " ${0##*/} -d sambacreate # Create a POSIX-compliant shell script named 'sambacreate'"
+}
+
+# Default shell to POSIX if no option is provided
+shell="sh"
+script_name=""
+
+# Parse options
+while [ "$1" ]; do
+ case "$1" in
+ -d | --dash)
+ shell="sh"
+ ;;
+ -b | --bash)
+ shell="bash"
+ ;;
+ -z | --zsh)
+ shell="zsh"
+ ;;
+ -h | --help)
+ usage
+ exit 0
+ ;;
+ -*)
+ echo "Error: Invalid option '$1'. Use -h or --help for usage information."
+ exit 1
+ ;;
+ *)
+ if [ -z "$script_name" ]; then
+ script_name="$1"
+ else
+ echo "Error: Multiple filenames provided. Use -h or --help for usage information."
+ exit 1
+ fi
+ ;;
+ esac
+ shift
+done
+
+# Validate script name
+if [ -z "$script_name" ]; then
+ echo "Error: No filename provided. Use -h or --help for usage information."
+ exit 1
+fi
+
+# Check if the script already exists
+script_path="$HOME/.local/bin"
+if [ -f "$script_path/$script_name" ]; then
+ echo "Error: File '$script_path/$script_name' already exists."
+ exit 1
+fi
+
+# Create the new script file
+mkdir -p "$script_path"
+
+echo "#!/bin/$shell
+
+# Help function
+usage() {
+ echo \"Usage: \${0##*/} [OPTION] ????\"
+ echo \"????\"
+ echo \"\"
+ echo \"Options:\"
+ echo \" -h, --help : Show this help message\"
+ echo \"\"
+ echo \"Example:\"
+ echo \" \${0##*/} -? ???? # ????\"
+}
+
+# Main function
+$script_name() {
+ case \"\$1\" in
+ -h | --help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo \"\${0##*/}\"
+ ;;
+ esac
+}
+
+# Call the main function with arguments
+$script_name \"\$@\"" >"$script_path/$script_name"
+
+# Make the script executable
+chmod +x "$script_path/$script_name"
+
+echo "'$script_name' created successfully at $script_path."
diff --git a/ar/.local/bin/cron/README.md b/ar/.local/bin/cron/README.md
new file mode 100644
index 0000000..fa0c354
--- /dev/null
+++ b/ar/.local/bin/cron/README.md
@@ -0,0 +1,11 @@
+# Important Note
+
+These cronjobs have components that require information about your current display to display notifications correctly.
+
+When you add them as cronjobs, I recommend you precede the command with commands as those below:
+
+```
+export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; then_command_goes_here
+```
+
+This ensures that notifications will display, xdotool commands will function and environmental variables will work as well.
diff --git a/ar/.local/bin/cron/checkup b/ar/.local/bin/cron/checkup
new file mode 100755
index 0000000..dfdfc4a
--- /dev/null
+++ b/ar/.local/bin/cron/checkup
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# Syncs repositories and downloads updates, meant to be run as a cronjob.
+
+notify-send "📦 Repository Sync" "Checking for package updates..."
+
+sudo pacman -Syyuw --noconfirm || notify-send "⛔ Error downloading updates.
+
+Check your internet connection, if pacman is already running, or run update manually to see errors."
+pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+
+if pacman -Qu | grep -v "\[ignored\]"; then
+ notify-send "🎁 Repository Sync" "Updates available. Click statusbar icon (📦) or run sb-popupgrade in terminal for update."
+else
+ notify-send "📦 Repository Sync" "Sync complete. No new packages for update."
+fi
diff --git a/ar/.local/bin/cron/crontog b/ar/.local/bin/cron/crontog
new file mode 100755
index 0000000..9ebab9c
--- /dev/null
+++ b/ar/.local/bin/cron/crontog
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Toggles all cronjobs off/on.
+# Stores disabled crontabs in ~/.config/crons until restored.
+
+CRON_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/crons"
+
+# Check if there are any active cronjobs
+if crontab -l 2>/dev/null | grep -q '^[^#[:space:]]'; then
+ # If active cronjobs are found, save and disable them
+ ln -sf "${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/$(whereami)/.config/crons" "${XDG_CONFIG_HOME:-${HOME}/.config}/crons"
+ crontab -r
+ notify-send "⏰ Cronjobs saved and disabled."
+else
+ # If no active cronjobs are found, try re-enabling from saved file
+ if [ -f "$CRON_FILE" ]; then
+ crontab - <"$CRON_FILE"
+ rm "$CRON_FILE"
+ notify-send "🕓 Cronjobs re-enabled."
+ else
+ notify-send "🕰️ No saved cronjobs to re-enable."
+ fi
+fi
+
+# Notify status bar to update
+pkill -RTMIN+3 "${STATUSBAR:-dwmblocks}"
diff --git a/ar/.local/bin/cron/mediaup b/ar/.local/bin/cron/mediaup
new file mode 100755
index 0000000..85935a2
--- /dev/null
+++ b/ar/.local/bin/cron/mediaup
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+timestamp_file="${HOME}/.cache/ytdlpupdate"
+current_time=$(date +%s)
+
+# Create cache directory if it doesn't exist
+mkdir -p "${HOME}/.cache"
+
+# Check if the timestamp file exists and is less than 24 hours old
+if [ -f "$timestamp_file" ] && [ "$(cat "$timestamp_file")" -gt "$((current_time - 86400))" ]; then
+ exit 0
+fi
+
+# Check if pipx is available, install if not
+if ! command -v pipx >/dev/null 2>&1; then
+ python3 -m pip install --user pipx || exit 1
+ python3 -m pipx ensurepath || exit 1
+ if [ -f "${ZDOTDIR:-${HOME}/.config/zsh}/.zshrc" ]; then
+ # shellcheck source=${ZDOTDIR:-${HOME}/.config/zsh}/.zshrc
+ . "${ZDOTDIR:-${HOME}/.config/zsh}/.zshrc"
+ elif [ -f "${HOME}/.zshrc" ]; then
+ # shellcheck source=${HOME}/.zshrc
+ . "${HOME}/.zshrc"
+ fi
+fi
+
+# Check if yt-dlp is installed via pipx, install or upgrade it
+if ! pipx list | grep -q yt-dlp; then
+ pipx install yt-dlp || exit 1
+else
+ pipx upgrade yt-dlp || exit 1
+fi
+
+echo "$current_time" >"$timestamp_file"
diff --git a/ar/.local/bin/cron/newsup b/ar/.local/bin/cron/newsup
new file mode 100755
index 0000000..346ec75
--- /dev/null
+++ b/ar/.local/bin/cron/newsup
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Set as a cron job to check for new RSS entries for newsboat.
+# If newsboat is open, sends it an "R" key to refresh.
+
+/usr/bin/notify-send "📰 Updating RSS feeds..."
+
+pgrep -f newsboat$ && /usr/bin/xdotool key --window "$(/usr/bin/xdotool search --name "^newsboat$")" R && exit
+
+echo 🔃 >/tmp/newsupdate
+pkill -RTMIN+19 "${STATUSBAR:-dwmblocks}"
+/usr/bin/newsboat -x reload
+rm -f /tmp/newsupdate
+pkill -RTMIN+19 "${STATUSBAR:-dwmblocks}"
+/usr/bin/notify-send "📰 RSS feed update complete."
diff --git a/ar/.local/bin/cutvideo b/ar/.local/bin/cutvideo
new file mode 100755
index 0000000..3220008
--- /dev/null
+++ b/ar/.local/bin/cutvideo
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+usage() {
+ echo "Crop a video file using ffmpeg."
+ echo ""
+ echo "Usage: cutvideo [file_name] [position] [duration]"
+ echo ""
+ echo "Arguments:"
+ echo " file_name: The name of the video file (e.g., video.mp4)."
+ echo " position: The start position in the format HH:MM:SS (e.g., 00:00:00)."
+ echo " duration: The duration in seconds from the start position (e.g., 10)."
+ echo ""
+ echo "Example:"
+ echo " cutvideo ~/Video/video.mp4 00:01:00 10"
+ echo " This will create a 10-second cut starting at 00:01:00 in the video.mp4 file."
+ exit 1
+}
+
+[ -z "$1" ] && echo "Target file missing" && usage
+[ -z "$2" ] && echo "Target position missing" && usage
+[ -z "$3" ] && echo "Target duration missing" && usage
+
+file="$1"
+filename="${file%%.*}"
+ext="${file##*.}"
+num=1
+
+# Find a unique filename by incrementing num
+if [ -f "${filename}_cut.${ext}" ]; then
+ while [ -f "${filename}_cut_$(printf "%02d" "$num").${ext}" ]; do
+ num=$((num + 1))
+ done
+ new_filename="${filename}_cut_$(printf "%02d" "$num").${ext}"
+else
+ new_filename="${filename}_cut.${ext}"
+fi
+
+# Perform the cut using ffmpeg
+ffmpeg -hide_banner -ss "$2" -to "$3" -i "$file" -c copy "$new_filename"
+
+echo "Created file: $new_filename"
diff --git a/ar/.local/bin/displayselect b/ar/.local/bin/displayselect
new file mode 100755
index 0000000..38fc942
--- /dev/null
+++ b/ar/.local/bin/displayselect
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+# A UI for detecting and selecting all displays. Probes xrandr for connected
+# displays and lets user select one to use. User may also select "manual
+# selection" which opens arandr.
+
+twoscreen() { # If multi-monitor is selected and there are two screens.
+ mirror=$(printf "no\\nyes" | dmenu -i -p "Mirror displays?")
+ # Mirror displays using native resolution of external display and a scaled
+ # version for the internal display
+ if [ "$mirror" = "yes" ]; then
+ external=$(echo "$screens" | dmenu -i -p "Optimize resolution for:")
+ internal=$(echo "$screens" | grep -v "$external")
+
+ res_external=$(xrandr --query | sed -n "/^$external/,/\+/p" |
+ tail -n 1 | awk '{print $1}')
+ res_internal=$(xrandr --query | sed -n "/^$internal/,/\+/p" |
+ tail -n 1 | awk '{print $1}')
+
+ res_ext_x=$(echo "$res_external" | sed 's/x.*//')
+ res_ext_y=$(echo "$res_external" | sed 's/.*x//')
+ res_int_x=$(echo "$res_internal" | sed 's/x.*//')
+ res_int_y=$(echo "$res_internal" | sed 's/.*x//')
+
+ scale_x=$(echo "$res_ext_x / $res_int_x" | bc -l)
+ scale_y=$(echo "$res_ext_y / $res_int_y" | bc -l)
+
+ xrandr --output "$external" --auto --scale 1.0x1.0 \
+ --output "$internal" --auto --same-as "$external" \
+ --scale "$scale_x"x"$scale_y"
+ else
+ primary=$(echo "$screens" | dmenu -i -p "Select primary display:")
+ secondary=$(echo "$screens" | grep -v ^"$primary"$)
+ direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?")
+ xrandr --output "$primary" --auto --scale 1.0x1.0 --output "$secondary" --"$direction"-of "$primary" --auto --scale 1.0x1.0
+ fi
+}
+
+morescreen() { # If multi-monitor is selected and there are more than two screens.
+ primary=$(echo "$screens" | dmenu -i -p "Select primary display:")
+ secondary=$(echo "$screens" | grep -v ^"$primary"$ | dmenu -i -p "Select secondary display:")
+ direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?")
+ tertiary=$(echo "$screens" | grep -v ^"$primary"$ | grep -v ^"$secondary"$ | dmenu -i -p "Select third display:")
+ xrandr --output "$primary" --auto --output "$secondary" --"$direction"-of "$primary" --auto --output "$tertiary" --"$(printf "left\\nright" | grep -v "$direction")"-of "$primary" --auto
+}
+
+multimon() { # Multi-monitor handler.
+ case "$(echo "$screens" | wc -l)" in
+ 2) twoscreen ;;
+ *) morescreen ;;
+ esac
+}
+
+onescreen() { # If only one output available or chosen.
+ xrandr --output "$1" --auto --scale 1.0x1.0 $(echo "$allposs" | grep -v "\b$1" | awk '{print "--output", $1, "--off"}' | paste -sd ' ' -)
+}
+
+postrun() { # Stuff to run to clean up.
+ setbg # Fix background if screen size/arangement has changed.
+ {
+ killall dunst
+ setsid -f dunst
+ } >/dev/null 2>&1 # Restart dunst to ensure proper location on screen
+}
+
+# Get all possible displays
+allposs=$(xrandr -q | grep "connected")
+
+# Get all connected screens.
+screens=$(echo "$allposs" | awk '/ connected/ {print $1}')
+
+# If there's only one screen
+[ "$(echo "$screens" | wc -l)" -lt 2 ] &&
+ {
+ onescreen "$screens"
+ postrun
+ notify-send "💻 Only one screen detected." "Using it in its optimal settings..."
+ exit
+ }
+
+# Get user choice including multi-monitor and manual selection:
+chosen=$(printf "%s\\nmulti-monitor\\nmanual selection" "$screens" | dmenu -i -p "Select display arangement:") &&
+ case "$chosen" in
+ "manual selection")
+ arandr
+ exit
+ ;;
+ "multi-monitor") multimon ;;
+ *) onescreen "$chosen" ;;
+ esac
+
+postrun
diff --git a/ar/.local/bin/dmenubrowse b/ar/.local/bin/dmenubrowse
new file mode 100755
index 0000000..a7446ac
--- /dev/null
+++ b/ar/.local/bin/dmenubrowse
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Use dmenu to choose a search option
+SEARCH_TOOL=$(printf "DuckDuckGo\nSearx\nWebsite\nYouTube" | dmenu -i -p "Which option?")
+
+# Exit if no option is selected
+[ -z "$SEARCH_TOOL" ] && exit 1
+
+# Determine the command to execute based on the search tool
+case "$SEARCH_TOOL" in
+"DuckDuckGo")
+ # For DuckDuckGo, run ddgr in the terminal
+ TOOL="$TERMINAL -e browse -d"
+ ;;
+"Searx")
+ # Searx can be run directly in the browser
+ TOOL="browse"
+ ;;
+"Website")
+ # Ask the user for the website
+ SITE=$(dmenu -i -p "Which site?")
+
+ # Exit if no site is provided
+ [ -z "$SITE" ] && exit 1
+
+ # For website searches, run ddgr in the terminal with the website option
+ TOOL="$TERMINAL -e browse -w $SITE"
+ ;;
+"YouTube")
+ TOOL="browse -y"
+ ;;
+*)
+ TOOL="browse"
+ ;;
+esac
+
+# Get the search query from the user
+SEARCH_QUERY=$(echo | dmenu -i -p "Search: ")
+
+# Exit if no search query is provided
+[ -z "$SEARCH_QUERY" ] && exit 1
+
+# Execute the command
+$TOOL "$SEARCH_QUERY"
diff --git a/ar/.local/bin/dmenudelmusic b/ar/.local/bin/dmenudelmusic
new file mode 100755
index 0000000..2efbafa
--- /dev/null
+++ b/ar/.local/bin/dmenudelmusic
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+music_dir="${XDG_MUSIC_DIR:-${HOME}/Music}"
+music_txt="${music_dir}/.music.txt"
+playlist_dir="${XDG_CONFIG_HOME:-${HOME}/.config}/mpd/playlists"
+
+# Using POSIX-compliant methods for file selection
+selected_filename=$(find "$music_dir" -type f | awk -F/ '{print $NF}' | dmenu -i -l 20 -p "Select a file to delete:") || exit 1
+
+selected_file="$music_dir/$selected_filename"
+
+# Extracting YouTube video ID without using -P in grep
+video_id=$(strings "$selected_file" | grep 'watch?v=' | sed 's/.*watch?v=\([a-zA-Z0-9_-]*\).*/\1/' | head -1) || {
+ notify-send "❌ No YouTube video ID found in file: $selected_filename"
+ exit 1
+}
+
+# Confirmation dialog without using echo -e
+confirm=$(printf "Yes\nNo" | dmenu -i -p "Delete $selected_filename and update mpc?")
+
+[ "$confirm" = "Yes" ] || {
+ notify-send "❌ Operation cancelled."
+ exit 0
+}
+
+# More portable sed command without -i and updating mpc
+if grep -v "$video_id" "$music_txt" >"${music_txt}.tmp" && mv "${music_txt}.tmp" "$music_txt"; then
+ # Search and remove the filename from playlists
+ for playlist in "$playlist_dir"/*.m3u; do
+ [ -e "$playlist" ] || continue
+ if grep -q "$selected_filename" "$playlist"; then
+ grep -v "$selected_filename" "$playlist" > "${playlist}.tmp" && mv "${playlist}.tmp" "$playlist"
+ # Remove empty lines
+ sed -i '/^$/d' "$playlist"
+ fi
+ done
+
+ # Delete the music file
+ rm "$selected_file"
+ mpc update >/dev/null
+ notify-send " Success to delete:" "$selected_filename"
+else
+ notify-send "❌ An error occurred."
+fi
diff --git a/ar/.local/bin/dmenuhandler b/ar/.local/bin/dmenuhandler
new file mode 100755
index 0000000..02e015e
--- /dev/null
+++ b/ar/.local/bin/dmenuhandler
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Feed this script a link and it will give dmenu
+# some choice programs to use to open it.
+feed="${1:-$(true | dmenu -p 'Paste URL or file path')}"
+
+case "$(printf "copy url\\nnsxiv\\nsetbg\\nPDF\\nbrowser\\nlynx\\nvim\\nmpv\\nmpv loop\\nmpv float\\nqueue download\\nqueue yt-dlp\\nqueue yt-dlp audio" | dmenu -i -p "Open it with?")" in
+"copy url") echo "$feed" | xclip -selection clipboard ;;
+mpv) setsid -f mpv -quiet "$feed" >/dev/null 2>&1 ;;
+"mpv loop") setsid -f mpv -quiet --loop "$feed" >/dev/null 2>&1 ;;
+"mpv float") setsid -f "$TERMINAL" -e mpv --geometry=+0-0 --autofit=30% --title="mpvfloat" "$feed" >/dev/null 2>&1 ;;
+"queue yt-dlp") qndl "$feed" >/dev/null 2>&1 ;;
+"queue yt-dlp audio") qndl "$feed" 'yt-dlp -o "%(title)s.%(ext)s" -f bestaudio --embed-metadata --restrict-filenames' ;;
+"queue download") qndl "$feed" 'curl -LO' >/dev/null 2>&1 ;;
+PDF) curl -sL "$feed" >"/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && zathura "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;;
+nsxiv) curl -sL "$feed" >"/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && nsxiv -a "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;;
+vim) curl -sL "$feed" >"/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && setsid -f "$TERMINAL" -e "$EDITOR" "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;;
+setbg)
+ curl -L "$feed" >"$XDG_CACHE_HOME/pic"
+ xwallpaper --zoom "$XDG_CACHE_HOME/pic" >/dev/null 2>&1
+ ;;
+browser) setsid -f "$BROWSER" "$feed" >/dev/null 2>&1 ;;
+lynx) lynx "$feed" >/dev/null 2>&1 ;;
+esac
diff --git a/ar/.local/bin/dmenuman b/ar/.local/bin/dmenuman
new file mode 100755
index 0000000..d571e60
--- /dev/null
+++ b/ar/.local/bin/dmenuman
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cmd=$(echo | dmenu -p "Enter command for man page:")
+
+[ -n "$cmd" ] && man_path=$(man -w "$cmd" 2>/dev/null) && [ -n "$man_path" ] && setsid -f "${TERMINAL:-st}" -e man "$cmd"
diff --git a/ar/.local/bin/dmenumountcifs b/ar/.local/bin/dmenumountcifs
new file mode 100755
index 0000000..46c2b57
--- /dev/null
+++ b/ar/.local/bin/dmenumountcifs
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Gives a dmenu prompt to mount unmounted local NAS shares for read/write.
+# Requirements - "%wheel ALL=(ALL) NOPASSWD: ALL"
+#
+# Browse for mDNS/DNS-SD services using the Avahi daemon...
+srvname=$(avahi-browse _smb._tcp -t | awk '{print $4}' | dmenu -i -p "Which NAS?") || exit 1
+notify-send "Searching for network shares..." "Please wait..."
+# Choose share disk...
+share=$(smbclient -L "$srvname" -N | grep Disk | awk '{print $1}' | dmenu -i -p "Mount which share?") || exit 1
+# Format URL...
+share2mnt=//"$srvname".local/"$share"
+
+sharemount() {
+ mounted=$(mount -v | grep "$share2mnt") || ([ ! -d /mnt/"$share" ] && sudo mkdir /mnt/"$share")
+ [ -z "$mounted" ] && sudo mount -t cifs "$share2mnt" -o user=nobody,password="",noperm /mnt/"$share" && notify-send "Netshare $share mounted" && exit 0
+ notify-send "Netshare $share already mounted"; exit 1
+}
+
+sharemount
diff --git a/ar/.local/bin/dmenupass b/ar/.local/bin/dmenupass
new file mode 100755
index 0000000..2c14e6f
--- /dev/null
+++ b/ar/.local/bin/dmenupass
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# This script is the SUDO_ASKPASS variable, meaning that it will be used as a
+# password prompt if needed.
+
+dmenu -fn Monospace-18 -P -p "$1" <&- && echo
diff --git a/ar/.local/bin/dmenurecord b/ar/.local/bin/dmenurecord
new file mode 100755
index 0000000..4aef54f
--- /dev/null
+++ b/ar/.local/bin/dmenurecord
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+usage() {
+ echo "Asks for recording type via dmenu."
+ echo "If there is already a running instance, user will be prompted to end it. "
+ echo ""
+ echo "Usage: ${0##*/} [-h] [audio] [kill] [video] [screencast]"
+ echo ""
+ echo "Options:"
+ echo " - audio : Records only audio"
+ echo " - kill : Kills existing recording"
+ echo " - video : Records only screen"
+ echo " - screencast : Records both audio and screen"
+}
+
+getdim() { xrandr | grep -oP '(?<=current ).*(?=,)' | tr -d ' '; }
+
+updateicon() {
+ echo "$1" >/tmp/recordingicon
+ pkill -RTMIN+31 "${STATUSBAR:-dwmblocks}"
+}
+
+killrecording() {
+ recpid="$(cat /tmp/recordingpid)"
+ kill -15 "$recpid"
+ rm -f /tmp/recordingpid
+ updateicon ""
+ pkill -RTMIN+31 "${STATUSBAR:-dwmblocks}"
+}
+
+screencast() {
+ ffmpeg -y \
+ -f x11grab \
+ -framerate 30 \
+ -s "$(getdim)" \
+ -i "$DISPLAY" \
+ -r 24 \
+ -use_wallclock_as_timestamps 1 \
+ -f alsa -thread_queue_size 1024 -i default \
+ -c:v h264 \
+ -crf 0 -preset ultrafast -c:a aac \
+ "$HOME/screencast-$(date '+%y%m%d-%H%M-%S').mp4" &
+ echo $! >/tmp/recordingpid
+ updateicon "⏺️🎙️"
+}
+
+video() {
+ ffmpeg \
+ -f x11grab \
+ -framerate 30 \
+ -s "$(getdim)" \
+ -i "$DISPLAY" \
+ -c:v libx264 -qp 0 -r 30 \
+ "$HOME/video-$(date '+%y%m%d-%H%M-%S').mkv" &
+ echo $! >/tmp/recordingpid
+ updateicon "⏺️"
+}
+
+webcamhidef() {
+ ffmpeg \
+ -f v4l2 \
+ -i /dev/video0 \
+ -video_size 1920x1080 \
+ "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" &
+ echo $! >/tmp/recordingpid
+ updateicon "🎥"
+}
+
+webcam() {
+ ffmpeg \
+ -f v4l2 \
+ -i /dev/video0 \
+ -video_size 640x480 \
+ "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" &
+ echo $! >/tmp/recordingpid
+ updateicon "🎥"
+}
+
+audio() {
+ ffmpeg \
+ -f alsa -i default \
+ -c:a flac \
+ "$HOME/audio-$(date '+%y%m%d-%H%M-%S').flac" &
+ echo $! >/tmp/recordingpid
+ updateicon "🎙️"
+}
+
+askrecording() {
+ choice=$(printf "screencast\\nvideo\\nvideo selected\\naudio\\nwebcam\\nwebcam (hi-def)" | dmenu -i -p "Select recording style:")
+ case "$choice" in
+ screencast) screencast ;;
+ audio) audio ;;
+ video) video ;;
+ *selected) videoselected ;;
+ webcam) webcam ;;
+ "webcam (hi-def)") webcamhidef ;;
+ esac
+}
+
+asktoend() {
+ response=$(printf "No\\nYes" | dmenu -i -p "Recording still active. End recording?") &&
+ [ "$response" = "Yes" ] && killrecording
+}
+
+videoselected() {
+ slop -f "%x %y %w %h" >/tmp/slop
+ read -r X Y W H </tmp/slop
+ rm /tmp/slop
+
+ ffmpeg \
+ -f x11grab \
+ -framerate 30 \
+ -video_size "$W"x"$H" \
+ -i :0.0+"$X,$Y" \
+ -c:v libx264 -qp 0 -r 30 \
+ "$HOME/box-$(date '+%y%m%d-%H%M-%S').mkv" &
+ echo $! >/tmp/recordingpid
+ updateicon "⏺️"
+}
+
+case "$1" in
+-h | --help | help) usage && exit 0 ;;
+audio) audio ;;
+kill) killrecording ;;
+screencast) screencast ;;
+video) video ;;
+*selected) videoselected ;;
+*) ([ -f /tmp/recordingpid ] && asktoend && exit) || askrecording ;;
+esac
diff --git a/ar/.local/bin/dmenusmbadd b/ar/.local/bin/dmenusmbadd
new file mode 100755
index 0000000..d1b76cd
--- /dev/null
+++ b/ar/.local/bin/dmenusmbadd
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+samba_conf="/etc/samba/smb.conf"
+base_path="/media /mnt /home/$USER"
+os="$(grep '^ID=' /etc/os-release | cut -d= -f2)"
+
+restartinit() {
+ case "$(basename "$(readlink -f /sbin/init)" | sed 's/-init//g')" in
+ *systemd*)
+ sudo systemctl restart smb >/dev/null/ 2>&1 && sudo systemctl restart nmb >/dev/null/ 2>&1
+ ;;
+ *runit*)
+ sudo sv restart smbd >/dev/null 2>&1 && sudo sv restart nmbd >/dev/null 2>&1
+ ;;
+ esac
+}
+
+[ -f "$samba_conf" ] || { sudo touch "$samba_conf" && smbpasswd -a "$USER"; }
+if [ "$#" -eq 1 ]; then
+ folder_path="$1"
+ folder_name=$(basename "$folder_path" | tr '[:upper:]' '[:lower:]')
+ if [ "$folder_path" = "/media/$USER" ]; then
+ folder_name="media"
+ fi
+else
+ folder_name=$(echo | dmenu -p "Enter the name of the folder to share:" | tr '[:upper:]' '[:lower:]') || exit 1
+ [ -z "$folder_name" ] && notify-send "📁No folder name provided." && exit 1
+
+ if [ "$folder_name" = "media" ]; then
+ target_name="$USER"
+ else
+ target_name="$folder_name"
+ fi
+
+ folder_path=$(for path in $base_path; do
+ find "$path" -type d -iname "$target_name" -print 2>/dev/null
+ done | sort -r | dmenu -l 10 -p "Select the folder to share:")
+ [ -z "$folder_path" ] && notify-send "📁Folder not found." && exit 1
+fi
+
+[ -d "$folder_path" ] || exit 1
+
+if grep -qF "[$USER-$os-$folder_name]" "$samba_conf"; then
+ notify-send "📁The folder '$target_name' is already shared."
+ exit 0
+fi
+
+echo | sudo tee -a "$samba_conf" >/dev/null
+sudo tee -a "$samba_conf" >/dev/null <<EOF && restartinit || exit 1
+[$USER-$os-$folder_name]
+ path = $folder_path
+ writable = yes
+ browsable = yes
+ guest ok = yes
+ create mask = 0755
+EOF
+
+notify-send "📁'$USER-$os-$folder_name' starts sharing." "path: '$folder_path'"
diff --git a/ar/.local/bin/dmenusmbdel b/ar/.local/bin/dmenusmbdel
new file mode 100755
index 0000000..5769695
--- /dev/null
+++ b/ar/.local/bin/dmenusmbdel
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+samba_conf="/etc/samba/smb.conf"
+dmenu_command=$(command -v dmenu)
+
+# Check if Samba configuration file exists
+[ -f "$samba_conf" ] || {
+ echo "Error: Samba configuration file not found at $samba_conf"
+ exit 1
+}
+
+# Extract share names and their paths for selection, with alignment
+shares=$(awk '
+/^\[.*\]/ {
+ share_name = $0
+ gsub(/[\[\]]/, "", share_name)
+}
+/^ *path *=/ {
+ sub(/^ *path *= */, "", $0)
+ printf "%-40s %s\n", share_name, $0
+}
+' "$samba_conf")
+
+# Check if dmenu is installed and available
+if [ -n "$dmenu_command" ]; then
+ selected=$(printf "%s\n" "$shares" | "$dmenu_command" -l 10 -p "Select a shared folder to disable:")
+
+ # Exit if no selection was made
+ [ -z "$selected" ] && exit 0
+
+ # Extract share name from the selected entry (before the spaces)
+ selected_share=$(echo "$selected" | awk '{print $1}')
+
+ # Confirm with the user
+ confirm=$(printf "Yes\nNo\n" | "$dmenu_command" -p "Disable sharing for $selected_share?")
+ if [ "$confirm" = "Yes" ]; then
+ # Remove only the selected share block and its preceding empty line
+ sed -n -e "/^$/N;/^\n\[${selected_share}\]/,/^ *create mask = 0755$/!p" "$samba_conf" | sudo tee "$samba_conf" >/dev/null
+
+ # Restart Samba services
+ case "$(basename "$(readlink -f /sbin/init)" | sed 's/-init//g')" in
+ *systemd*)
+ sudo systemctl restart smb >/dev/null 2>&1 && sudo systemctl restart nmb >/dev/null 2>&1
+ ;;
+ *runit*)
+ sudo sv restart smbd >/dev/null 2>&1 && sudo sv restart nmbd >/dev/null 2>&1
+ ;;
+ esac
+
+ notify-send "✂️ Disabled sharing for '$selected_share'"
+ fi
+else
+ # Print share names if dmenu is not installed
+ printf "%s\n" "$shares"
+fi
diff --git a/ar/.local/bin/dmenuunicode b/ar/.local/bin/dmenuunicode
new file mode 100755
index 0000000..eb5ff4c
--- /dev/null
+++ b/ar/.local/bin/dmenuunicode
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# The famous "get a menu of emojis to copy" script.
+
+# Get user selection via dmenu from emoji file.
+chosen=$(cut -d ';' -f1 ~/.local/share/thesiah/chars/* | dmenu -i -l 30 | sed "s/ .*//")
+
+# Exit if none chosen.
+[ -z "$chosen" ] && exit
+
+# If you run this command with an argument, it will automatically insert the
+# character. Otherwise, show a message that the emoji has been copied.
+if [ -n "$1" ]; then
+ xdotool type "$chosen"
+else
+ printf "%s" "$chosen" | xclip -selection clipboard
+ notify-send "'$chosen' copied to clipboard." &
+fi
diff --git a/ar/.local/bin/dmenuupgrade b/ar/.local/bin/dmenuupgrade
new file mode 100755
index 0000000..b43ff4c
--- /dev/null
+++ b/ar/.local/bin/dmenuupgrade
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+# Get list of pacman and yay upgradable packages
+pacman_updates=$(pacman -Qu)
+yay_updates=$(yay -Qu)
+
+# Count the number of upgradable packages, filtering yay's output to count only AUR packages
+pacman_count=$(printf "%s" "$pacman_updates" | grep -c '^\S')
+aur_count=$(printf "%s" "$yay_updates" | grep -c '^\[AUR\]')
+
+# Display the upgrade options with package counts
+selection=$(printf "All\nPacman(%d)\nAUR(%d)" "$pacman_count" "$aur_count" | dmenu -i -p "Upgrade Option:")
+
+case "$selection" in
+"All")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Proceed with upgrade for all packages?')" = "Yes" ]; then
+ notify-send "📦 Upgrading all packages..."
+ yay -Syu --noconfirm
+ pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+ notify-send "✅ Upgrade of all packages completed."
+ else
+ notify-send "❌ Upgrade cancelled."
+ exit 0
+ fi
+ ;;
+"Pacman($pacman_count)")
+ # Show all upgradable pacman packages with "Upgrade All" option
+ selection=$(printf "Upgrade All\n%s" "$pacman_updates" | dmenu -i -l 10 -p "Pacman: Upgrade or Open URL:")
+ case "$selection" in
+ "Upgrade All")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Proceed with pacman upgrade?')" = "Yes" ]; then
+ notify-send "📦 Upgrading pacman packages..."
+ printf "%s" "$pacman_updates" | awk '{print $1}' | xargs sudo pacman -S --noconfirm
+ pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+ notify-send "✅ Pacman packages upgrade completed."
+ else
+ notify-send "❌ Upgrade cancelled."
+ exit 0
+ fi
+ ;;
+ *)
+ # Individual package selected: Prompt to upgrade or open URL
+ action=$(printf "Upgrade\nOpen URL" | dmenu -i -p "Package: $selection")
+ case "$action" in
+ "Upgrade")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Proceed with upgrade for this package?')" = "Yes" ]; then
+ notify-send "📦 Upgrading package: $selection..."
+ sudo pacman -S --noconfirm "$(printf "%s" "$selection" | awk '{print $1}')"
+ pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+ notify-send "✅ Upgrade completed for package: $selection."
+ else
+ notify-send "❌ Upgrade cancelled."
+ exit 0
+ fi
+ ;;
+ "Open URL")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Open URL?')" = "Yes" ]; then
+ xdg-open "https://archlinux.org/packages/?q=$(printf "%s" "$selection" | awk '{print $1}')" >/dev/null 2>&1 &
+ else
+ exit
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+"AUR($aur_count)")
+ # Show all upgradable AUR packages with "Upgrade All" option
+ aur_updates=$(printf "%s" "$yay_updates" | grep '^\[AUR\]')
+ selection=$(printf "Upgrade All\n%s" "$aur_updates" | dmenu -i -l 10 -p "AUR: Upgrade or Open URL:")
+ case "$selection" in
+ "Upgrade All")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Proceed with AUR upgrade?')" = "Yes" ]; then
+ notify-send "📦 Upgrading AUR packages..."
+ yay -Syu --aur --noconfirm
+ pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+ notify-send "✅ AUR packages upgrade completed."
+ else
+ notify-send "❌ Upgrade cancelled."
+ exit 0
+ fi
+ ;;
+ *)
+ # Individual AUR package selected: Prompt to upgrade or open URL
+ action=$(printf "Upgrade\nOpen URL" | dmenu -i -p "Package: $selection")
+ case "$action" in
+ "Upgrade")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Proceed with upgrade for this package?')" = "Yes" ]; then
+ notify-send "📦 Upgrading AUR package: $selection..."
+ yay -S --noconfirm "$(printf "%s" "$selection" | awk '{print $2}')"
+ pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+ notify-send "✅ Upgrade completed for AUR package: $selection."
+ else
+ notify-send "❌ Upgrade cancelled."
+ exit 0
+ fi
+ ;;
+ "Open URL")
+ if [ "$(printf "No\nYes" | dmenu -i -p 'Open URL?')" = "Yes" ]; then
+ xdg-open "https://aur.archlinux.org/packages/?O=0&K=$(printf "%s" "$selection" | awk '{print $2}')" >/dev/null 2>&1 &
+ else
+ exit
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+*)
+ notify-send "❌ Invalid selection."
+ exit 0
+ ;;
+esac
+
+remaps
diff --git a/ar/.local/bin/dvdburn b/ar/.local/bin/dvdburn
new file mode 100755
index 0000000..0e78b83
--- /dev/null
+++ b/ar/.local/bin/dvdburn
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Check if input file is provided
+if [ $# -eq 0 ]; then
+ echo "Usage: $0 input_video.mp4"
+ exit 1
+fi
+
+# Check if input file exists
+input_file="$1"
+if [ ! -f "$input_file" ]; then
+ echo "Error: Input file '$input_file' not found."
+ exit 1
+fi
+
+# Set default VIDEO_FORMAT if not provided
+VIDEO_FORMAT="${VIDEO_FORMAT:-PAL}"
+
+# Create temporary directory for DVD structure
+tmp_dir=$(mktemp -d)
+echo "Temporary directory created: $tmp_dir"
+
+# Convert MP4 to DVD-compatible MPEG-2 format
+echo "Converting $input_file to DVD-Video format ($VIDEO_FORMAT)..."
+ffmpeg -i "$input_file" -target "$VIDEO_FORMAT"-dvd -vf scale=720:576 -aspect 16:9 "$tmp_dir/video.mpg"
+
+# Create DVD file structure
+echo "Creating DVD file structure..."
+dvdauthor -o "$tmp_dir/dvd" -t "$tmp_dir/video.mpg"
+dvdauthor -o "$tmp_dir/dvd" -T
+
+# Create ISO image from DVD structure
+echo "Creating ISO image..."
+mkisofs -dvd-video -o "$tmp_dir/dvd.iso" "$tmp_dir/dvd/"
+
+# Burn ISO image to DVD
+echo "Burning DVD..."
+wodim -v dev=/dev/sr0 speed=8 -eject "$tmp_dir/dvd.iso"
+
+# Cleanup
+echo "Cleaning up..."
+rm -rf "$tmp_dir"
+
+echo "Done!"
diff --git a/ar/.local/bin/ecrypt b/ar/.local/bin/ecrypt
new file mode 100755
index 0000000..5547dcb
--- /dev/null
+++ b/ar/.local/bin/ecrypt
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+mount_encrypted() {
+ ! mount | grep -q " $1 " && echo "$PASSPHRASE" | sudo mount -t ecryptfs "$1" "$2" \
+ -o ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=no,ecryptfs_enable_filename_crypto=yes,ecryptfs_sig=$ECRYPTFS_SIG,ecryptfs_fnek_sig=$FNEK_SIG,passwd=$(printf '%s' "$PASSPHRASE")
+}
+
+attempt_mount() {
+ if mount | grep -q " $2 "; then
+ sudo umount "$2" && notify-send "🔒 Locked: $3" || notify-send "❗ Unable to lock" "Mounted: $3"
+ else
+ ECRYPTFS_SIG=$(pass show encryption/ecryptfs-sig-"$4")
+ FNEK_SIG=$ECRYPTFS_SIG
+ PASSPHRASE=$(pass show encryption/ecryptfs)
+ [ -z "$PASSPHRASE" ] && {
+ notify-send "❌ Failed to retrieve passphrase."
+ exit 1
+ }
+ mount_encrypted "$1" "$2" && notify-send "🔑 Unlocked: $3"
+ fi
+}
+
+targets="$HOME/.secret"
+mounts="$HOME/Private"
+pw="default"
+set -- $mounts # Set positional parameters to mounts
+i=1
+for target in $targets; do
+ mp=$(eval echo "\$$i") # Get the mount point using indirect expansion
+ path=$(basename "$mp") # Extract last directory component
+ pw=$(echo "$pw" | cut -d' ' -f$i) # Get the corresponding passthrough option
+
+ attempt_mount "$target" "$mp" "$path" "$pw"
+ i=$((i + 1))
+done
diff --git a/ar/.local/bin/emojiupdate b/ar/.local/bin/emojiupdate
new file mode 100755
index 0000000..cc2f1c5
--- /dev/null
+++ b/ar/.local/bin/emojiupdate
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# Define input and output files
+URL="https://unicode.org/Public/emoji/latest/emoji-test.txt"
+INPUT_FILE="${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/char/emoji_raw"
+TEMP_FILE="${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/char/emoji_temp"
+OUTPUT_FILE="${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/char/emoji"
+
+# Create the directory for output files if it doesn't exist
+mkdir -p "$(dirname "$INPUT_FILE")"
+
+# Download the emoji file
+echo "Downloading emoji-test.txt from Unicode..."
+if curl -o "$INPUT_FILE" -L "$URL"; then
+ echo "Download complete! File saved to: $INPUT_FILE"
+else
+ echo "Failed to download emoji"
+ exit 1
+fi
+
+awk '
+ # Skip empty lines and comments
+ /^[[:space:]]*$/ || /^#/ { next }
+
+ # Keep only fully-qualified lines
+ !/(fully-qualified|component)/ { next }
+
+ # Skip lines containing 200D (zero-width joiner)
+ /200D/ { next }
+
+ # Skip lines containing components
+ $2 ~ /1F3F[BCDEF]/ { next }
+
+ # Print valid lines
+ { print }
+' "$INPUT_FILE" >"$TEMP_FILE"
+
+# Second stage: Extract emoji and description
+awk -F'#' '
+ {
+ if (NF >= 2) {
+ full_data = $2 # Extract the emoji and description (after #)
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", full_data) # Trim spaces around the entire field
+
+ split(full_data, parts, " ") # Split into parts by spaces
+ emoji = parts[1] # First part is the emoji
+
+ # Reconstruct description from parts[3] onward
+ description = ""
+ for (i = 3; i <= length(parts); i++) {
+ description = description parts[i] " "
+ }
+
+ # Remove excessive internal spaces and trim description
+ gsub(/[[:space:]]+/, " ", description)
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", description)
+
+ # Print emoji and description
+ print emoji, description
+ }
+ }
+' "$TEMP_FILE" >"$OUTPUT_FILE"
+
+rm -rf "$INPUT_FILE" "$TEMP_FILE"
+echo "Processing complete! File saved to: $OUTPUT_FILE"
diff --git a/ar/.local/bin/ethwifi b/ar/.local/bin/ethwifi
new file mode 100755
index 0000000..1f76ace
--- /dev/null
+++ b/ar/.local/bin/ethwifi
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+check_ethernet() {
+ interfaces=$(ip link | awk '/^[0-9]+: e/ {print substr($2, 1, length($2)-1)}')
+ for iface in $interfaces; do
+ ip link show "$iface" | grep -q 'state UP' && return 0
+ done
+ return 1
+}
+
+check_ethernet && nmcli radio wifi off && notify-send "📡 wifi: OFF" || {
+ nmcli radio wifi on
+ notify-send "📡 wifi: ON"
+}
+
+pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}"
diff --git a/ar/.local/bin/extractkeys b/ar/.local/bin/extractkeys
new file mode 100755
index 0000000..4a8d82d
--- /dev/null
+++ b/ar/.local/bin/extractkeys
@@ -0,0 +1,142 @@
+#!/bin/bash
+
+# Define paths and phrases
+path="${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwm/thesiah"
+readme_file="${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/thesiah.mom"
+output_file="$path.mom"
+temp_file_before="$path.tmp.before"
+temp_file_after="$path.tmp.after"
+start_phrase="When config.h in DWM and ST is saved, key bindings will be extracted and updated below."
+end_phrase=".HEADING 2 \"Other buttons\""
+
+[ -f "$path.mom" ] && source_file="$path.mom" || source_file="$path-default.mom"
+
+# Extract the section before the end marker
+sed "/$end_phrase/,\$d" "$source_file" >"$temp_file_before"
+
+# Extract the section from the end marker to the end of the file
+sed -n "/$end_phrase/,\$p" "$source_file" >"$temp_file_after"
+
+# Remove the contents between the start and end phrases, including the markers themselves
+sed -i "/$start_phrase/,/$end_phrase/d" "$temp_file_before"
+
+# Verify that the section has been removed
+echo "Section between markers removed. Check $temp_file_before for correctness."
+
+# Re-append the start marker since it was deleted
+echo "$start_phrase" >>"$temp_file_before"
+
+# Define your configuration files
+mapfile -t config_files <<EOF
+${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwm/config.h
+${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/st/config.h
+EOF
+
+# Process each configuration file and append content
+for file_path in "${config_files[@]}"; do
+ project_name=$(basename "$(dirname "$file_path")")
+ project_name=${project_name^^}
+
+ echo ".HEADING 3 \"$project_name\"" >>"$temp_file_before"
+ if [ "$project_name" == "DWM" ]; then
+ printf ".PP\nTHESIAH's window manager. I do not use a desktop environment.\n" >>"$temp_file_before"
+ elif [ "$project_name" == "ST" ]; then
+ printf ".PP\nTHESIAH's default terminal in C. It is light, configurable, and fast.\n" >>"$temp_file_before"
+ fi
+ echo ".LI" >>"$temp_file_before"
+
+ awk 'BEGIN {flag=0} /static[[:space:]]+(const[[:space:]]+)?(Key|Shortcut|Command|Button)[[:space:]]+(keys|cmdkeys|shortcuts|commands|buttons)[[:space:]]*\[\][[:space:]]*=[[:space:]]*{/ {flag=1} /\};/ {flag=0} flag' "$file_path" | while read -r line; do
+ if [[ "$line" =~ \/\*.*\*\/ || "$line" =~ .*\"\\.* || "$line" =~ ^$ || "$line" =~ STACKKEYS || "$line" =~ TAGKEYS || "$line" =~ static\ Key\ cmdkeys || "$line" =~ ^\#.* ]]; then
+ continue
+ fi
+
+ if [[ "$line" =~ ^\/\/.* ]]; then
+ echo ".LIST OFF" >>"$temp_file_before"
+ line=$(echo "$line" | sed -e 's/\/\/\s*//g' | awk '{for (i=1; i<=NF; i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2)); print}')
+ output_line=".HEADING 5 \"$line\""
+ echo "$output_line" >>"$temp_file_before"
+ echo ".LI" >>"$temp_file_before"
+ continue
+ fi
+
+ if [[ "$line" =~ static\ const|static\ Shortcut ]]; then
+ echo ".LIST OFF" >>"$temp_file_before"
+ output_line=".HEADING 4 \"INSERT MODE\""
+ echo "$output_line" >>"$temp_file_before"
+ echo ".LI" >>"$temp_file_before"
+ continue
+ fi
+
+ if [[ "$line" =~ static\ Command ]]; then
+ echo ".LIST OFF" >>"$temp_file_before"
+ output_line=".HEADING 4 \"COMMAND MODE\""
+ echo "$output_line" >>"$temp_file_before"
+ echo ".LI" >>"$temp_file_before"
+ continue
+ fi
+
+ line=$(echo "$line" | sed -e "s/^{ \(.*\) },/\1/g;s/{ \([^,]*\), \([^,]*\), \([^,]*\), \([^}]*\) }/\1 \2 \3 \4/g")
+ modkey=$(echo "$line" | awk -F',' '{print $1}' | sed "s/\b0//g;s/MODKEY\(\d*\)/MOD\1/g;s/\([Shift|Contrl]\)Mask/\1/g;s/Control/Ctrl/g;s/TERMMOD/MOD+Shift/g;s/|/+/g;s/ //g;s/XK_ANY_MOD/ANY MOD/g")
+ key=$(echo "$line" | awk -F',' '{print $2}' | sed "s/\b0//g;s/XF86XK_//g;s/XK_//g;s/\s*\(.*\)_R$/Right_\1/g;s/\s*\(.*\)_L$/Left_\1/g;s/MODKEY\(\d*\)/MOD\1/g;s/\([Shift|Contrl]\)Mask/\1/g;s/Control/Ctrl/g;s/|/+/g;s/ //g")
+ func=$(echo "$line" | awk -F',' '{print $3}' | grep -v "spawn" | sed "s/ //g")
+ args=$(echo "$line" | cut -d',' -f4- | sed -E 's/.*\.v\s*=\s*\(const\s*char\s*\*\[\]\)\s*\{\s*([^}]*)\s*\}.*/\1/g;
+ s/.*\.v\s*=\s*\(int\s*\[\]\)\s*\{\s*([^}]*)\s*\}.*/\1/g;
+ s/.*SHCMD\((.*)\).*/\1/g;
+ s/.*\{\s*\.\w*\s*=\s*(.*)\s*\}.*/\1/g;
+ s/\{0\}//g;
+ s/\"//g;
+ s/\,//g;
+ s/NULL//g;
+ s/TERMINAL\s*-e\s*//g;
+ s/&layouts\[0\]/"[]="/g;
+ s/&layouts\[1\]/"[M]"/g;
+ s/&layouts\[2\]/"|||"/g;
+ s/&layouts\[3\]/"[@]"/g;
+ s/&layouts\[4\]/"[\\\\]"/g;
+ s/&layouts\[5\]/"H[]"/g;
+ s/&layouts\[6\]/"TTT"/g;
+ s/&layouts\[7\]/"==="/g;
+ s/&layouts\[8\]/"HHH"/g;
+ s/&layouts\[9\]/"###"/g;
+ s/&layouts\[10\]/"---"/g;
+ s/&layouts\[11\]/":::"/g;
+ s/&layouts\[12\]/"|M|"/g;
+ s/&layouts\[13\]/">M>"/g;
+ s/^\s*//g;
+ /^\s*0\s*$/d
+ ')
+
+ # args=$(echo "$line" | cut -d',' -f4- | sed "s/\.v = (const char \*\[\])//g;s/{\s*\.v = (int \[\])//g;s/SHCMD(\(.*\))/\1/g;s/\.. = //g")
+
+ if [[ -z "$modkey" ]]; then
+ if [[ -z $func ]]; then
+ output_line="\\f(CW${key}\\fP \\(en ${args}"
+ else
+ output_line="\\f(CW${key}\\fP \\(en ${func} ${args}"
+ fi
+ else
+ if [[ -z $func ]]; then
+ output_line="\\f(CW${modkey}+${key}\\fP \\(en ${args}"
+ else
+ output_line="\\f(CW${modkey}+${key}\\fP \\(en ${func} ${args}"
+ fi
+ fi
+
+ echo ".ITEM" >>"$temp_file_before"
+ echo "$output_line" >>"$temp_file_before"
+ done
+
+ echo ".LIST OFF" >>"$temp_file_before"
+done
+
+# Append the latter part of the document that follows the end marker
+cat "$temp_file_after" >>"$temp_file_before"
+
+# Replace the original file with the updated content
+mv -f "$temp_file_before" "$output_file"
+cp -f "$output_file" "$readme_file"
+
+# Clean up the temporary after-section file
+rm -f "$temp_file_after"
+
+notify-send "👍 Operation completed successfully."
diff --git a/ar/.local/bin/fzffiles b/ar/.local/bin/fzffiles
new file mode 100755
index 0000000..f6708c1
--- /dev/null
+++ b/ar/.local/bin/fzffiles
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# The set -e option instructs sh to immediately exit if any command has a non-zero exit status
+set -e
+
+# Set new line and tab for word splitting
+IFS='
+'
+
+# Get the list of selected files with key bindings for specific paths
+files=$(fzf-tmux \
+ --header "^a pwd ^b public ^d .dotfiles ^f configs ^g git ^h home ^k desktop ^r scripts ^s suckless ^u staged files ^v private ^/ help" \
+ --preview 'if [ -d {} ]; then exa --color=always --long --all --header --icons --git {}; elif [ -L {} ]; then file -h {}; else bat --color=always --style=header,grid --line-range=:500 {}; fi' \
+ --reverse \
+ --query="$1" \
+ --multi \
+ --exit-0 \
+ --prompt " 💡 " \
+ --bind "ctrl-a:change-prompt( ⚡ )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . $PWD)" \
+ --bind "ctrl-b:change-prompt( 🌎 )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . ${XDG_PUBLICSHARE_DIR:-${HOME}/Public})" \
+ --bind "ctrl-d:change-prompt( ⚙️ )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . ${XDG_DOTFILES_DIR:-${HOME}/.dotfiles})" \
+ --bind "ctrl-f:change-prompt( 🗂️ )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . ${XDG_CONFIG_HOME:-${HOME}/.config})" \
+ --bind "ctrl-g:change-prompt(  )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . $HOME/Private/repos $HOME/Public/repos)" \
+ --bind "ctrl-h:change-prompt( 🏠 )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . $HOME)" \
+ --bind "ctrl-k:change-prompt( 🖥️ )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . ${XDG_DESKTOP_DIR:-${HOME}/Desktop})" \
+ --bind "ctrl-r:change-prompt( 👟 )+reload(fd -H -L -t f -E .Trash -E .git -E .cache -E zsh . ${XDG_BIN_HOME:-${HOME}/.local/bin})" \
+ --bind "ctrl-s:change-prompt( 🛠 )+reload(find ${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless -maxdepth 2 -type f -not -path '*/.git/*')" \
+ --bind "ctrl-u:change-prompt( 📝 )+reload(if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then git -C $(git rev-parse --show-toplevel 2>/dev/null) status -s | awk -v pwd=\"$PWD\" '{print pwd \"\/\" \$2}' | grep -v '^$'; else echo 'This is not a git repository.'; fi)" \
+ --bind "ctrl-v:change-prompt( 🔒 )+reload(fd -H -L -t f -E .Trash -E .git -E .cache . $HOME/Private)" \
+ --bind 'ctrl-/:change-prompt( ❓ )+reload(echo "^a all
+^b public
+^c configs
+^d .dotfiles
+^g git
+^k desktop
+^r scripts
+^s suckless
+^u staged files
+^v private
+^/ help")')
+
+# Check if any files were selected, and exit if not
+[ -z "$files" ] && exit 0
+
+openfiles "$files"
diff --git a/ar/.local/bin/fzffns b/ar/.local/bin/fzffns
new file mode 100755
index 0000000..c919723
--- /dev/null
+++ b/ar/.local/bin/fzffns
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+# Print all functions and comments in a readable format
+# Set the filename of the script containing the functions
+file="${ZDOTDIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/zsh}/scripts.zsh"
+
+# Initialize an empty variable to hold functions, aliases, and comments
+functions=""
+
+# Parse the file for function names, aliases, and comments
+while IFS= read -r line || [ -n "$line" ]; do
+ case "$line" in
+ \#*)
+ if [ "$(printf '%s' "$line" | cut -c 2)" != "#" ] && [ "$(printf '%s' "$line" | cut -c 2)" != "!" ]; then
+ # Remove the '#' from the comment line
+ comment=$(printf '%s' "$line" | sed 's/^# //')
+
+ # Read the next line to check for alias or function definition
+ IFS= read -r next_line || break
+
+ # Check if it's an alias definition
+ if echo "$next_line" | grep -q '^alias '; then
+ alias_name=$(echo "$next_line" | sed -n 's/^alias \([a-zA-Z0-9_]*\)=.*$/\1/p')
+
+ # Read another line to get the function definition
+ IFS= read -r func_line || break
+ f_name=$(printf '%s' "$func_line" | sed -n 's/^function \([^(]*\)().*$/\1/p')
+
+ if [ -n "$f_name" ]; then
+ functions="$functions$f_name|$alias_name|$comment\n"
+ fi
+
+ # Check if it's a function definition
+ elif echo "$next_line" | grep -q '^function '; then
+ f_name=$(printf '%s' "$next_line" | sed -n 's/^function \([^(]*\)().*$/\1/p')
+ if [ -n "$f_name" ]; then
+ functions="$functions$f_name||$comment\n"
+ fi
+ fi
+ fi
+ ;;
+ esac
+done <"$file"
+
+# Sort the functions alphabetically by name
+sorted=$(printf '%b' "$functions" | sort)
+
+# Print out the sorted functions with aliases and comments in a readable format
+formatted=$(printf '%b' "$sorted" | while IFS='|' read -r f_name alias_name comment; do
+ if [ -n "$alias_name" ]; then
+ printf 'fn: %-30s - %s (alias: %s)\n' "$f_name" "$comment" "$alias_name"
+ else
+ printf 'fn: %-30s - %s\n' "$f_name" "$comment"
+ fi
+done)
+
+# Use fzf to select a function
+selected=$(printf '%b' "$formatted" | fzf-tmux --header "Select a function:" --reverse)
+
+# Check if a function was selected
+if [ -n "$selected" ]; then
+ # Extract the function name
+ f_name=$(echo "$selected" | cut -d ' ' -f 2 | sed 's/[[:space:]]\+//g')
+
+ # Get the line number of the function definition
+ line_number=$(grep -n "^function $f_name(" "$file" | cut -d ':' -f 1)
+
+ # Open the function in nvim at the specific line number
+ if [ -n "$line_number" ]; then
+ nvim "+$line_number" "$file"
+ else
+ echo "Function '$f_name' not found in $file."
+ fi
+fi
diff --git a/ar/.local/bin/fzfpass b/ar/.local/bin/fzfpass
new file mode 100755
index 0000000..13cb6a6
--- /dev/null
+++ b/ar/.local/bin/fzfpass
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+set -e
+
+usage() {
+ echo "Find pass gpg files in Password-Store using fzf."
+ echo ""
+ echo "Usage: ${0##*/} [-h|--help]"
+ echo ""
+ echo "Options:"
+ echo " -h, --help : Show this message"
+ echo ""
+ echo "Shortcuts:"
+ echo " return - echo password and copy to clipboard (wayland only)"
+ echo " ctrl+a - new password (named as per the prompt)"
+ echo " ctrl+e - edit selected password"
+ echo " ctrl+g - regenerate selected password"
+ echo " ctrl+r - rename selected password"
+ echo " ctrl+x - delete selected password"
+ echo " tab - tab complete"
+ echo " esc/ctrl+c - exit"
+ exit 0
+}
+
+if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
+ usage
+fi
+
+PASSDIR=${PASSWORD_STORE_DIR:-$HOME/.local/share/.password-store}
+cd "$PASSDIR"
+
+# Unlock the password for this session
+pass show "$(tree -Ffi | grep '.gpg' | sed 's/.gpg$//g' | sed 's/^..//' | head -n 1)" >/dev/null
+
+# Main fzf session
+PASSFILE=$(
+ tree -Ffi | grep '.gpg' | sed 's/.gpg$//g' | sed 's/^..//' |
+ fzf-tmux \
+ --header="🔑 Password Manager" \
+ --reverse \
+ --no-mouse \
+ --preview="pass {}" \
+ --header="🔑 ^a: Generate | ^e: Edit | ^g: Generate (Insertion) | ^r: Rename | ^x: Delete | tab: Replace" \
+ --bind="ctrl-a:execute(if [ -z {q} ]; then read -p \"Can't generate empty password. Press enter to continue...\"; else pass generate -n {q} < /dev/tty > /dev/tty 2>&1 && pass edit {q} < /dev/tty > /dev/tty 2>&1; fi)+reload(tree -Ffi | grep '.gpg' | sed 's/.gpg$//g')" \
+ --bind="ctrl-e:execute(pass edit {} < /dev/tty > /dev/tty 2>&1)+reload(tree -Ffi | grep '.gpg' | sed 's/.gpg$//g')" \
+ --bind="ctrl-r:execute(bash -c '
+echo -n \"Enter new name for {}: \" > /dev/tty;
+read new_name < /dev/tty;
+if [ -n \"\$new_name\" ]; then
+ pass mv {} \"\$new_name\" || echo \"Rename failed.\" > /dev/tty;
+else
+ echo \"No name entered. Rename aborted.\" > /dev/tty;
+fi' < /dev/tty > /dev/tty 2>&1)+reload(tree -Ffi | grep '.gpg' | sed 's/.gpg$//g')" \
+ --bind="ctrl-g:execute(if [ -z {} ]; then read -p \"Can't generate empty password. Press enter to continue...\"; else pass generate -in {} < /dev/tty > /dev/tty 2>&1 && pass edit {} < /dev/tty > /dev/tty 2>&1; fi)+reload(tree -Ffi | grep '.gpg' | sed 's/.gpg$//g')" \
+ --bind="ctrl-x:execute(pass rm {} < /dev/tty > /dev/tty 2>&1)+reload(tree -Ffi | grep '.gpg' | sed 's/.gpg$//g')" \
+ --bind="tab:replace-query"
+)
+
+PASSDATA="$(pass "$PASSFILE")"
+PASSWORD="$(echo "$PASSDATA" | head -n 1)"
+SHOW_PASSDATA=false
+
+if [ "$1" = "-i" ]; then
+ SHOW_PASSDATA=true
+ shift
+fi
+
+if [ "$SHOW_PASSDATA" = true ]; then
+ PASSDATA="$(pass "$PASSFILE")"
+ echo "$PASSDATA"
+else
+ PASSWORD="$(pass show "$PASSFILE" | head -n 1)"
+ echo "$PASSWORD"
+
+ if [ -n "$PASSWORD" ]; then
+ case "$(uname)" in
+ Darwin*)
+ echo "$PASSWORD" | pbcopy # Use pbcopy on macOS
+ ;;
+ Linux*)
+ echo "$PASSWORD" | xclip -selection clipboard # Use xclip on Linux
+ ;;
+ *)
+ echo "Unsupported operating system"
+ ;;
+ esac
+ sleep 0.1
+ fi
+fi
diff --git a/ar/.local/bin/getbib b/ar/.local/bin/getbib
new file mode 100755
index 0000000..121dd6e
--- /dev/null
+++ b/ar/.local/bin/getbib
@@ -0,0 +1,14 @@
+#!/bin/sh
+[ -z "$1" ] && echo "Give either a pdf file or a DOI as an argument." && exit
+
+if [ -f "$1" ]; then
+ # Try to get DOI from pdfinfo or pdftotext output.
+ doi=$(pdfinfo "$1" | grep -io "doi:.*") ||
+ doi=$(pdftotext "$1" 2>/dev/null - | sed -n '/[dD][oO][iI]:/{s/.*[dD][oO][iI]:\s*\(\S\+[[:alnum:]]\).*/\1/p;q}') ||
+ exit 1
+else
+ doi="$1"
+fi
+
+# Check crossref.org for the bib citation.
+curl -s "https://api.crossref.org/works/$doi/transform/application/x-bibtex" -w "\\n"
diff --git a/ar/.local/bin/getcomproot b/ar/.local/bin/getcomproot
new file mode 100755
index 0000000..dbee348
--- /dev/null
+++ b/ar/.local/bin/getcomproot
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# A helper script for LaTeX/groff files used by `compiler` and `opout`.
+# The user can add the root file of a larger project as a comment as below:
+# % root = mainfile.tex
+# And the compiler script will run on that instead of the opened file.
+
+texroot="$(sed -n 's/^\s*%.*root\s*=\s*\(\S\+\).*/\1/p' "${1}")"
+[ -f "${texroot}" ] && readlink -f "${texroot}" || exit "1"
diff --git a/ar/.local/bin/getkeys b/ar/.local/bin/getkeys
new file mode 100755
index 0000000..79a7872
--- /dev/null
+++ b/ar/.local/bin/getkeys
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Print available keys from thesiah
+cat "${XDG_DATA_HOME:-${HOME}/.local/share}"/thesiah/keys/"$1" 2>/dev/null && exit
+echo "Run command with one of the following arguments for info about that program:"
+ls "${XDG_DATA_HOME:-${HOME}/.local/share}"/thesiah/keys
diff --git a/ar/.local/bin/gitfiles b/ar/.local/bin/gitfiles
new file mode 100755
index 0000000..510b032
--- /dev/null
+++ b/ar/.local/bin/gitfiles
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+# Exit immediately if any command fails
+set -e
+
+get_full_file_list() {
+ echo "$existing_files" | awk '!seen[$0]++'
+}
+
+handle_fzf_error() {
+ if [ $? -eq 130 ]; then
+ # If fzf-tmux was interrupted by Ctrl+C (exit code 130), exit gracefully
+ exit 0
+ else
+ # Otherwise, re-raise the error
+ return $?
+ fi
+}
+
+# Get the repository root path and change to the repo root directory
+repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
+cd "$repo_root" || {
+ echo "Failed to change to repository root directory"
+ exit 1
+}
+
+# Determine the base branch (main or master)
+if git show-ref --quiet refs/heads/main; then
+ base_branch=main
+elif git show-ref --quiet refs/heads/master; then
+ base_branch=master
+else
+ base_branch=main
+fi
+
+if [ "$(git remote | head -n 1)" = "origin" ]; then
+ remote="origin"
+elif [ "$(git remote | head -n 1)" = "home" ]; then
+ remote="home"
+else
+ remote="origin"
+fi
+
+merge_base=$(git merge-base HEAD "$base_branch")
+file_list=$(git log --pretty=format: --name-only -n 30 | grep . | awk '!seen[$0]++' | head -n 30)
+
+# Generate the file list and verify each file path
+existing_files=$(echo "$file_list" | while IFS= read -r file; do
+ [ -f "$file" ] && echo "$repo_root/$file"
+done)
+
+# Use fzf-tmux to select from the sorted list
+selected_files=$(get_full_file_list | fzf-tmux \
+ --header "^a: all, ^e: edited, ^f: current branch ^r: recent, ^s: staged, ^u: unpushed" \
+ --preview "bat --color=always {}" \
+ --reverse \
+ --multi \
+ --select-1 \
+ --exit-0 \
+ --bind "ctrl-a:reload(git ls-tree -r HEAD --name-only || handle_fzf_error)" \
+ --bind "ctrl-e:reload(git diff --name-only || handle_fzf_error)" \
+ --bind "ctrl-f:reload(git diff-tree --no-commit-id --name-only -r $merge_base..HEAD || handle_fzf_error)" \
+ --bind "ctrl-r:reload(echo \"$existing_files\" | awk '!seen[\$0]++' || handle_fzf_error)" \
+ --bind "ctrl-s:reload(git diff --cached --name-only || handle_fzf_error)" \
+ --bind "ctrl-u:reload(git diff --name-only $remote/$base_branch..HEAD || handle_fzf_error)" \
+ --bind "change:top" ||
+ handle_fzf_error)
+
+# Check if any files were selected, and exit if not
+[ -z "$selected_files" ] && exit 0
+
+openfiles "$selected_files"
diff --git a/ar/.local/bin/gitopenbranch b/ar/.local/bin/gitopenbranch
new file mode 100755
index 0000000..56c8d12
--- /dev/null
+++ b/ar/.local/bin/gitopenbranch
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+if ! command -v gh >/dev/null 2>&1; then
+ echo "Error: GitHub CLI (gh) is not installed." >&2
+ exit 1
+fi
+
+case "$(uname -s)" in
+Darwin)
+ open_cmd='open'
+ ;;
+*)
+ open_cmd='xdg-open'
+ ;;
+esac
+
+# Check if inside a git repository
+if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
+ current_branch=$(git rev-parse --abbrev-ref HEAD)
+ gh repo view --branch "$current_branch" --web || $open_cmd "$(gh repo view --branch "$current_branch" --json url -q '.url')"
+else
+ echo "Not a git repository."
+ exit 1
+fi
diff --git a/ar/.local/bin/gitstagedfiles b/ar/.local/bin/gitstagedfiles
new file mode 100755
index 0000000..1cdd902
--- /dev/null
+++ b/ar/.local/bin/gitstagedfiles
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Use fzf to select files and store them in a string with newline as a separator
+IFS='
+'
+files=$(git status -s | awk '$1 != "D" {print $2}' | grep -v '^$' | fzf-tmux --preview "bat --color=always {}" --reverse --multi --select-1 --exit-0)
+
+# Check if any files were selected, and exit if not
+[ -z "$files" ] && exit 0
+
+openfiles "$files"
diff --git a/ar/.local/bin/gitupdate b/ar/.local/bin/gitupdate
new file mode 100755
index 0000000..a214950
--- /dev/null
+++ b/ar/.local/bin/gitupdate
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+set -eu
+
+pidof transmission-daemon >/dev/null && echo "Turn off transmission-daemon first!" && exit 1
+
+# Check if inside a Git repository
+! git rev-parse --is-inside-work-tree >/dev/null 2>&1 && echo "Not a git repository." && exit 1
+
+# Function to generate a message for each status line
+generate_message() {
+ status=$1
+ file=$2
+ if echo "$file" | grep -q '/'; then
+ file_name="${file##*/}"
+ path_name="${file%/*}"
+ dir_name="${path_name##*/}"
+ dir_file_name="$dir_name/$file_name"
+ else
+ dir_file_name="$file"
+ fi
+ case "$status" in
+ " M" | "M ") # Modified
+ echo "modified $dir_file_name"
+ ;;
+ "??") # Untracked (new) files
+ echo "created $dir_file_name"
+ ;;
+ " D" | "D ") # Deleted from the index
+ echo "deleted $dir_file_name"
+ ;;
+ *) # Catch-all for other statuses
+ echo "updated $dir_file_name"
+ ;;
+ esac
+}
+
+# Check for changes in the working directory
+changes=$(git status --porcelain)
+
+# Check for unpushed commits
+unpushed=$(git cherry -v | wc -l)
+
+[ -z "$changes" ] && [ "$unpushed" -eq 0 ] && exit
+
+# Generate default commit message if there are changes
+if [ -n "$changes" ] && [ "$unpushed" -eq 0 ]; then
+ num_changes=$(echo "$changes" | wc -l)
+ if [ "$num_changes" -gt 5 ]; then
+ default="updates"
+ else
+ default=$(echo "$changes" | while IFS= read -r line; do
+ status=$(echo "$line" | cut -c1-2)
+ file=$(echo "$line" | cut -c4-)
+ generate_message "$status" "$file"
+ done | tr '\n' ',' | sed 's/,$//;s/,/, /g;s/"$//')
+ fi
+ message="${1:-$default}"
+
+ # Add and commit changes
+ git add --all .
+ git commit -m "$message"
+else
+ message=$(git cherry -v | awk '{$1=$2=""; print $0}' | sed 's/^ *//' | tail -n 1)
+fi
+
+# Get the current Git branch name
+branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
+
+# Save current directory and change to the Git repo root
+repo_root=$(git rev-parse --show-toplevel || echo ".")
+cd "$repo_root"
+
+# Pull and rebase before pushing
+git pull --rebase origin "$branch"
+
+# Check if the 'home' remote exists
+if git remote | grep -q "^home$"; then
+ # Push to both 'home' and 'origin'
+ git push home "$branch" && git push origin "$branch"
+else
+ # Push only to 'origin'
+ git push origin "$branch"
+fi
+
+printf "\n[repo] %s(%s): %s\n" "$(basename "$repo_root")" "$branch" "$message"
+
+# Return to the original directory
+cd - >/dev/null
+
+command -v dwmblocks >/dev/null 2>&1 && pkill -RTMIN+18 "${STATUSBAR:-dwmblocks}"
diff --git a/ar/.local/bin/gpt b/ar/.local/bin/gpt
new file mode 100755
index 0000000..393916a
--- /dev/null
+++ b/ar/.local/bin/gpt
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+set -e
+
+# Check if input is piped
+if [ -t 0 ]; then
+ input="$1"
+else
+ input=$(cat | sed -E "s/\x1b\[[0-9;]*m//g")
+fi
+
+# Use a temporary file for the processed content
+tmpfile=$(mktemp /tmp/nvim_buffer_cleaned.XXXXXX)
+
+# Save the input to the temporary file
+echo "$input" >"$tmpfile"
+
+# Process the input and open Neovim directly, ensuring it doesn't suspend
+nvim \
+ -c "GpChatNew" \
+ -c "call append(line('\$')-1, readfile('$tmpfile'))" \
+ -c "normal! Gdd" \
+ -c "startinsert"
+
+# Remove the temporary file after usage
+rm "$tmpfile"
diff --git a/ar/.local/bin/gracefulkill b/ar/.local/bin/gracefulkill
new file mode 100755
index 0000000..918ab79
--- /dev/null
+++ b/ar/.local/bin/gracefulkill
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -euo pipefail
+
+changedVimFocused() {
+ title=$(getFocusedTitle)
+ [[ $title == *VIM\ \+\" ]]
+}
+
+getFocusedConId() {
+ i3-msg -t get_tree | jq 'recurse | objects | select(.type=="con" and .focused==true) | .id'
+}
+
+getFocusedTitle() {
+ id=$(getFocusedConId)
+ i3-msg -t get_tree | jq "recurse | objects | select(.id == $id) | .name"
+}
+
+killFocusedContainer() {
+ id=$(getFocusedConId)
+ i3-msg "[con_id=\"$id\"]" kill
+}
+
+if ! changedVimFocused; then
+ killFocusedContainer
+fi
diff --git a/ar/.local/bin/ifinstalled b/ar/.local/bin/ifinstalled
new file mode 100755
index 0000000..d0bd26b
--- /dev/null
+++ b/ar/.local/bin/ifinstalled
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# Some optional functions in THESIAH require programs not installed by default. I
+# use this little script to check to see if a command exists and if it doesn't
+# it informs the user that they need that command to continue. This is used in
+# various other scripts for clarity's sake.
+
+for x in "$@"; do
+ if ! which "$x" >/dev/null 2>&1 && ! pacman -Qq "$x" >/dev/null 2>&1; then
+ notify-send "📦 $x" "must be installed for this function." && exit 1
+ fi
+done
diff --git a/ar/.local/bin/iwaf b/ar/.local/bin/iwaf
new file mode 100755
index 0000000..7e3aad3
--- /dev/null
+++ b/ar/.local/bin/iwaf
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+_check_wine() {
+ WINE_EXE="$1"
+
+ ls "${WINE_EXE}" >/dev/null 2>/dev/null
+}
+
+_exec_wine() {
+ WINE_EXE="$1"
+ WINE_EXE2="${WINE_EXE%/*}"
+ WINE_EXE2="${WINE_EXE2}/SOOPStreamer.exe"
+
+ echo 'Executing Soop...'
+ wine "${WINE_EXE}"
+ # wine "${WINE_EXE2}"
+}
+
+_install_wine() {
+ # Source URL for the SOOPStreamer installer
+ SRC_URL='https://creatorup.sooplive.co.kr/SOOPStreamer_installer.exe'
+
+ # Create a temporary file for the installer
+ DST_FILE="$(mktemp)"
+ # Ensure the temporary file is removed after the script finishes
+ trap 'rm -f "$DST_FILE"' EXIT
+
+ # Set a temporary HOME for the Wine process to avoid conflicts
+ HOME_ORIGIN="$HOME"
+ HOME_PATCH="$WINEPREFIX/tmp"
+ export HOME="$HOME_PATCH"
+
+ # Download the installer
+ echo 'Downloading SOOP Streamer installer...'
+ curl -s "$SRC_URL" -o "$DST_FILE" || {
+ echo "Failed to download installer!"
+ exit 1
+ }
+
+ # Initialize Wine environment with win32 architecture
+ echo 'Initializing Wine environment...'
+ wineboot >/dev/null 2>&1
+
+ # Check if winetricks is installed, if not, install it
+ if ! command -v winetricks >/dev/null; then
+ sudo pacman --noconfirm --needed -S winetricks || {
+ echo "Failed to install winetricks!"
+ exit 1
+ }
+ fi
+
+ # Install necessary libraries via winetricks
+ echo "Installing required libraries via winetricks..."
+ if ! winetricks -q mfc42 vcrun2008; then
+ echo "Failed to install required libraries!"
+ exit 1
+ fi
+
+ # Run the SOOP Streamer installer without sudo
+ echo 'Running the SOOP Streamer installer...'
+ wine "$DST_FILE" /S >/dev/null 2>&1 && success=true || success=false
+
+ # Clean up temporary Wine HOME directory
+ echo 'Cleaning up...'
+ rm -rf "$HOME_PATCH"
+
+ # Restore original HOME and WINEARCH variables
+ export HOME="$HOME_ORIGIN"
+
+ # Check success status and print the result
+ if $success; then
+ echo 'Installation completed successfully.'
+ else
+ echo 'Installation failed!'
+ fi
+}
+
+# Define a main function
+main() {
+ # Configure environment variables
+ export LANG='en_US.UTF-8'
+ export LC_ALL='en_US.UTF-8'
+ export WINEARCH='win32'
+ export WINEPREFIX="${WINEPREFIX:-${XDG_DATA_HOME:-${HOME}/.local/share}/wine}/soop"
+
+ # Install
+ WINE_EXE="${WINEPREFIX}/drive_c/users/$(whoami)/AppData/Local/SOOP/SOOPPackage.exe"
+ if ! _check_wine "${WINE_EXE}"; then
+ _install_wine "${WINE_EXE}"
+ fi
+
+ # Exec
+ _exec_wine "${WINE_EXE}"
+}
+
+main
diff --git a/ar/.local/bin/lastnvim b/ar/.local/bin/lastnvim
new file mode 100755
index 0000000..b1ab6c9
--- /dev/null
+++ b/ar/.local/bin/lastnvim
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Display help message
+usage() {
+ echo "Open the most recent file or the list of old files in fzf edited by nvim."
+ echo ""
+ echo "Usage: ${0##*/} [OPTION]"
+ echo ""
+ echo "Options:"
+ echo " : Open the most recent old file in Neovim."
+ echo " -h, --help : Show this help message."
+ echo " -l, --list : Show all recent files in Neovim using fzf."
+ echo ""
+ echo "Examples:"
+ echo " ${0##*/} # Open the most recent file."
+ echo " ${0##*/} -l # Show all recent files in fzf and select to open."
+ exit 0
+}
+
+# List and handle oldfiles
+list_oldfiles() {
+ # Fetch the oldfiles list from Neovim
+ oldfiles=$(nvim -u NONE --headless +'lua io.write(table.concat(vim.v.oldfiles, "\n") .. "\n")' +qa)
+
+ # Exit if no oldfiles are found
+ [ -z "$oldfiles" ] && {
+ echo "No recent files found in Neovim." >&2
+ exit 1
+ }
+
+ case "$1" in
+ -h | --help)
+ usage
+ ;;
+ -l | --list)
+ # Filter valid files
+ valid_files=$(echo "$oldfiles" | while IFS= read -r file; do
+ [ -f "$file" ] && printf "%s\n" "$file"
+ done)
+
+ # Exit if no valid files exist
+ [ -z "$valid_files" ] && {
+ echo "No valid files found." >&2
+ exit 1
+ }
+
+ # Use fzf to select files
+ selected_files=$(echo "$valid_files" |
+ fzf-tmux \
+ --multi \
+ --preview 'bat -n --color=always --line-range=:500 {} 2>/dev/null || echo "Error previewing file"' \
+ --height=70% \
+ --reverse)
+
+ # Exit if no files were selected
+ [ -z "$selected_files" ] && exit 1
+
+ # Open selected files in Neovim
+ openfiles "$selected_files"
+ ;;
+ *)
+ # Open the most recent file
+ for file in $oldfiles; do
+ if [ -f "$file" ]; then
+ openfiles "$file"
+ exit 0
+ fi
+ done
+
+ echo "No valid recent files found." >&2
+ exit 1
+ ;;
+ esac
+}
+
+# Parse command-line arguments
+list_oldfiles "$@"
diff --git a/ar/.local/bin/lfub b/ar/.local/bin/lfub
new file mode 100755
index 0000000..b743989
--- /dev/null
+++ b/ar/.local/bin/lfub
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# This is a wrapper script for lf that allows it to create image previews with
+# ueberzug. This works in concert with the lf configuration file and the
+# lf-cleaner script.
+
+set -e
+
+cleanup() {
+ exec 3>&-
+ rm "$FIFO_UEBERZUG"
+}
+
+if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
+ lf "$@"
+else
+ [ ! -d "$HOME/.cache/lf" ] && mkdir -p "$HOME/.cache/lf"
+ export FIFO_UEBERZUG="$HOME/.cache/lf/ueberzug-$$"
+ mkfifo "$FIFO_UEBERZUG"
+ ueberzug layer -s -p json <"$FIFO_UEBERZUG" &
+ exec 3>"$FIFO_UEBERZUG"
+ trap cleanup HUP INT QUIT TERM PWR EXIT
+ lf "$@" 3>&-
+fi
diff --git a/ar/.local/bin/linkhandler b/ar/.local/bin/linkhandler
new file mode 100755
index 0000000..7dccafc
--- /dev/null
+++ b/ar/.local/bin/linkhandler
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Feed script a url or file location.
+# If an image, it will view in nsxiv,
+# if a video or gif, it will view in mpv
+# if a music file or pdf, it will download,
+# otherwise it opens link in browser.
+
+if [ -z "$1" ]; then
+ url="$(xclip -o)"
+else
+ url="$1"
+fi
+
+case "$url" in
+*mkv | *webm | *mp4 | *youtube.com/watch* | *youtube.com/playlist* | *youtube.com/shorts* | *youtu.be* | *hooktube.com* | *bitchute.com* | *videos.lukesmith.xyz* | *odysee.com*)
+ setsid -f mpv -quiet "$url" >/dev/null 2>&1
+ ;;
+*png | *jpg | *jpe | *jpeg | *gif | *webp)
+ curl -sL "$url" >"/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" && nsxiv -a "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" >/dev/null 2>&1 &
+ ;;
+*pdf | *cbz | *cbr)
+ curl -sL "$url" >"/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" && zathura "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" >/dev/null 2>&1 &
+ ;;
+*mp3 | *flac | *opus | *mp3?source*)
+ qndl "$url" 'curl -LO' >/dev/null 2>&1
+ ;;
+*)
+ [ -f "$url" ] && setsid -f "$TERMINAL" -e "$EDITOR" "$url" >/dev/null 2>&1 || setsid -f "$BROWSER" "$url" >/dev/null 2>&1
+ ;;
+esac
diff --git a/ar/.local/bin/maimpick b/ar/.local/bin/maimpick
new file mode 100755
index 0000000..06d9092
--- /dev/null
+++ b/ar/.local/bin/maimpick
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# This is bound to Shift+PrintScreen by default, requires maim. It lets you
+# choose the kind of screenshot to take, including copying the image or even
+# highlighting an area to copy. scrotcucks on suicidewatch right now.
+
+# variables
+output_dir="${XDG_PICTURES_DIR:-${HOME}/Pictures}/screenshots/"
+output="$(date '+%y%m%d-%H%M-%S').png"
+xclip_cmd="xclip -sel clip -t image/png"
+ocr_cmd="xclip -sel clip"
+
+[ -d "$output_dir" ] || mkdir -p "$output_dir"
+
+case "$(printf "a selected area\\ncurrent window\\nfull screen\\na selected area (copy)\\ncurrent window (copy)\\nfull screen (copy)\\ncopy selected image to text" | dmenu -l 7 -i -p "Screenshot which area?")" in
+"a selected area") maim -u -s "$output_dir"pic-selected-"${output}" ;;
+"current window") maim -B -q -d 0.2 -i "$(xdotool getactivewindow)" "$output_dir"pic-window-"${output}" ;;
+"full screen") maim -q -d 0.2 "$output_dir"pic-full-"${output}" ;;
+"a selected area (copy)") maim -u -s | ${xclip_cmd} ;;
+"current window (copy)") maim -q -d 0.2 -i "$(xdotool getactivewindow)" | ${xclip_cmd} ;;
+"full screen (copy)") maim -q -d 0.2 | ${xclip_cmd} ;;
+"copy selected image to text") tmpfile=$(mktemp /tmp/ocr-XXXXXX.png) && maim -u -s >"$tmpfile" && tesseract "$tmpfile" - -l eng | ${ocr_cmd} && rm "$tmpfile" ;;
+esac
diff --git a/ar/.local/bin/mbackup b/ar/.local/bin/mbackup
new file mode 100755
index 0000000..71856b1
--- /dev/null
+++ b/ar/.local/bin/mbackup
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+batch_file="${XDG_MUSIC_DIR:-${HOME}/Music}/.music.txt"
+backup="/tmp/music_backup.txt"
+[ -f "$batch_file" ] || exit 1
+echo "Backing up music files..."
+[ -f "$backup" ] && rm -f "$backup" >/dev/null 2>&1
+while read -r line; do
+ video_id="${line#* }"
+ echo "https://www.youtube.com/watch?v=$video_id" >>"$backup"
+done <"$batch_file"
+yt-dlp --continue --embed-metadata --ignore-errors --no-force-overwrites --verbose --audio-format best --audio-quality 0 --batch-file "$backup" --extract-audio --recode-video mp3 --output "${XDG_MUSIC_DIR:-${HOME}/Music}/%(artist)s - %(title)s.%(ext)s" >/dev/null 2>&1
+rm -f "$backup" >/dev/null 2>&1
+echo "Done!"
diff --git a/ar/.local/bin/monitorbright b/ar/.local/bin/monitorbright
new file mode 100755
index 0000000..2de2e5d
--- /dev/null
+++ b/ar/.local/bin/monitorbright
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+monitor=$(xrandr --query | grep -i '\sconnected' | grep '[0-9]x[0-9]' | grep -i 'primary' | cut -d ' ' -f1)
+[ -z "$monitor" ] && monitor=$(xrandr --query | grep -i '\sconnected' | grep '[0-9]x[0-9]' | cut -d ' ' -f1)
+case "$monitor" in
+*DP* | *HDMI*)
+ current_brightness=$(xrandr --verbose | grep -i "^$monitor connected" -A5 | grep -i "Brightness:" | cut -d ' ' -f2)
+ [ -z "$current_brightness" ] && exit 1
+ if [ "$#" -eq 2 ]; then
+ scale_change=$(echo "$2 / 100" | bc -l)
+ case "$1" in
+ "-inc") new_brightness=$(echo "$current_brightness + $scale_change" | bc -l) ;;
+ "-dec") new_brightness=$(echo "$current_brightness - $scale_change" | bc -l) ;;
+ *) echo "Invalid argument $1. Use -inc or -dec." && exit 1 ;;
+ esac
+ new_brightness=$(echo "if ($new_brightness > 1) 1 else if ($new_brightness < 0) 0 else $new_brightness" | bc -l)
+ xrandr --output "$monitor" --brightness "$new_brightness"
+ current_brightness=$(echo "$new_brightness * 100" | bc -l)
+ else
+ current_brightness=$(echo "$current_brightness * 100" | bc -l)
+ fi
+ printf "🪟%.0f%%\n" "$current_brightness"
+ ;;
+esac
diff --git a/ar/.local/bin/mounter b/ar/.local/bin/mounter
new file mode 100755
index 0000000..6f294d2
--- /dev/null
+++ b/ar/.local/bin/mounter
@@ -0,0 +1,181 @@
+#!/bin/bash
+
+# Mounts Android Phones and USB drives (encrypted or not). This script will
+# replace the older `dmenumount` which had extra steps and couldn't handle
+# encrypted drives.
+# TODO: Try decrypt for drives in crtypttab
+# TODO: Add some support for connecting iPhones (although they are annoying).
+
+IFS='
+'
+# Function for escaping cell-phone names.
+escape() { echo "$@" | iconv -cf UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g"; }
+
+# Function for prompting user for a mountpoint.
+getmount() {
+ [ -z "$1" ] && mp="$(find "/media/$USER" /mnt -mindepth 1 -maxdepth 1 -type d 2>/dev/null | dmenu -i -p "Mount this drive where?")" || mp="$1"
+ test -n "$mp"
+ if [ ! -d "$mp" ]; then
+ mkdiryn=$(printf "No\\nYes" | dmenu -i -p "$mp does not exist. Create it?")
+ [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" 2>/dev/null || sudo -A mkdir -p "$mp")
+ fi
+}
+
+attemptmount() {
+ # Attempt to mount without a mountpoint, to see if drive is in fstab.
+ mplabel=$(sudo lsblk -no "label" "$chosen")
+ mp="/media/$USER/$mplabel"
+ [ -n "$mplabel" ] || return
+ if [ ! -d "$mp" ] && [ ! -d "/mnt/$mplabel" ]; then
+ getmount "$mp" && sudo -A mount "$chosen" "$mp" >/dev/null 2>&1 || return 1
+ elif [ -d "$mp" ] && [ ! -d "/mnt/$mplabel" ]; then
+ sudo -A mount "$chosen" "$mp" >/dev/null 2>&1 || return 1
+ elif [ -d "/mnt/$mplabel" ]; then
+ getmount "/mnt/$mplabel" && sudo -A mount "$chosen" "/mnt/$mplabel" >/dev/null 2>&1 || return 1
+ else
+ sudo -A mount "$chosen" >/dev/null 2>&1 || return 1
+ fi
+}
+
+# Check for phones.
+phones="$(simple-mtpfs -l 2>/dev/null | sed "s/^/📱/")"
+mountedphones="$(grep "simple-mtpfs" /etc/mtab)"
+# If there are already mounted phones, remove them from the list of mountables.
+[ -n "$mountedphones" ] && phones="$(for phone in $phones; do
+ for mounted in $mountedphones; do
+ escphone="$(escape "$phone")"
+ [[ "$mounted" =~ $escphone ]] && break 1
+ done && continue 1
+ echo "$phone"
+done)"
+
+# Check for drives.
+lsblkoutput="$(sudo lsblk -rpo "uuid,name,type,size,label,mountpoint,fstype")"
+# Get all LUKS drives
+allluks="$(echo "$lsblkoutput" | grep crypto_LUKS)"
+# Get a list of the LUKS drive UUIDs already decrypted.
+decrypted="$(find /dev/disk/by-id/dm-uuid-CRYPT-LUKS2-* | sed "s|.*LUKS2-||;s|-.*||")"
+# Functioning for formatting drives correctly for dmenu:
+filter() { sed "s/ /:/g" | awk -F':' '$7==""{printf "%s%s (%s) %s\n",$1,$3,$5,$6}'; }
+
+# Get only LUKS drives that are not decrypted.
+unopenedluks="$(for drive in $allluks; do
+ uuid="${drive%% *}"
+ uuid="${uuid//-/}" # This is a bashism.
+ [ -n "$decrypted" ] && for open in $decrypted; do
+ [ "$uuid" = "$open" ] && break 1
+ done && continue 1
+ echo "🔒 $drive"
+done | filter)"
+
+# Get all normal, non-encrypted or decrypted partitions that are not mounted.
+normalparts="$(echo "$lsblkoutput" | grep -v crypto_LUKS | grep 'part\|rom\|crypt' | sed "s/^/💾 /" | filter)"
+
+# Get all available IP addresses with open Samba shares in the wlan0 subnet, excluding eth0 IP
+smbips="$(sudo arp-scan --interface=wlan0 --localnet | grep -vEi '(EFM Networks|locally administered|DUP:)' | awk '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/{print $1}')"
+
+# Get currently mounted CIFS shares
+mountedsmbs="$(grep 'cifs' /etc/mtab)"
+
+for smbip in $smbips; do
+ smb=$(
+ smbclient -L "$smbip" -U% -A /dev/stdin <<EOF 2>/dev/null | awk '/Disk/ {print $1}' | grep -vEi '(ADMIN|\w)\$'
+username=$(whoami)
+password=$(pass show default/default)
+EOF
+ )
+ win=$(
+ smbclient -L "$smbip" -U% -A /dev/stdin <<EOF 2>/dev/null | awk '/Disk/ {print $1}' | grep -vEi '(ADMIN|\w)\$'
+username=$(whoami)
+password=$(pass show default/windows)
+EOF
+ )
+ while IFS= read -r share; do
+ if ! echo "$smbshares" | grep -q "$share"; then
+ smbshares+="//$smbip/$share"$'\n'
+ fi
+ done <<<"$smb"
+ while IFS= read -r share; do
+ if ! echo "$smbshares" | grep -q "$share"; then
+ smbshares+="//$smbip/$share"$'\n'
+ fi
+ done <<<"$win"
+done
+
+smbshares="$(echo "$smbshares" | sed '/^$/d')"
+
+[ -n "$smbshares" ] && smbs="$(echo "$smbshares" | while IFS= read -r smb; do
+ [ -n "$mountedsmbs" ] && for mountedsmb in $mountedsmbs; do
+ mountedsmb="${mountedsmb%% *}"
+ [[ "$mountedsmb" =~ $smb ]] && break 1
+ done && continue 1
+ echo "📡 $smb"
+done)"
+
+# Add all to one variable. If no mountable drives found, exit.
+alldrives="$(echo "$phones
+$unopenedluks
+$normalparts
+$smbs" | sed "/^$/d;s/ *$//")"
+
+# Quit the script if a sequential command fails.
+set -e
+test -n "$alldrives" || { echo "No mountable drives" && exit; }
+
+# Feed all found drives to dmenu and get user choice.
+chosen="$(echo "$alldrives" | dmenu -p "Mount which drive?" -i)"
+case "$chosen" in
+💾*)
+ chosen="${chosen%% *}"
+ chosen="${chosen:1}" # This is a bashism.
+ parttype="$(echo "$lsblkoutput" | grep "$chosen")"
+ attemptmount || {
+ getmount
+ case "${parttype##* }" in
+ vfat) sudo -A mount -t vfat "$chosen" "$mp" -o rw,umask=0000 ;;
+ btrfs) sudo -A mount "$chosen" "$mp" ;;
+ *) sudo -A mount "$chosen" "$mp" -o uid="$(id -u)",gid="$(id -g)" ;;
+ esac
+ }
+ notify-send "💾 Drive Mounted." "$chosen mounted to $mp."
+ ;;
+🔒*)
+ chosen="${chosen%% *}"
+ chosen="${chosen:1}" # This is a bashism.
+ # Number the drive.
+ while [ -e "/dev/mapper/usb$num" ]; do
+ num=$((num + 1))
+ done
+ pass show default/default | sudo cryptsetup open "$chosen" "usb$num" || ${TERMINAL:-st} -n floatterm -g 60x1 -e sudo cryptsetup open "$chosen" "usb$num"
+ # Check if now decrypted.
+ test -b "/dev/mapper/usb$num"
+ chosen="/dev/mapper/usb$num"
+ attemptmount || {
+ getmount
+ sudo -A mount "/dev/mapper/usb$num" "$mp" -o uid="$(id -u)",gid="$(id -g)" || sudo -A mount "/dev/mapper/usb$num" "$mp"
+ }
+ notify-send "🔓 Decrypted drive Mounted." "$chosen decrypted and mounted to $mp."
+ ;;
+📱*)
+ notify-send "❗ Note" "Remember to allow file access on your phone now."
+ getmount
+ number="${chosen%%:*}"
+ number="${chosen:1}" # This is a bashism.
+ sudo -A simple-mtpfs -o allow_other -o fsname="simple-mtpfs-$(escape "$chosen")" --device "$number" "$mp"
+ notify-send "🤖 Android Mounted." "Android device mounted to $mp."
+ ;;
+📡*)
+ chosen="${chosen##* }"
+ path="${chosen##*/}"
+ if getmount "/media/$USER/$path"; then
+ sudo -A mount -t cifs -o username="$(whoami)",password="$(pass show default/default)" "$chosen" "$mp" 2>/dev/null ||
+ sudo -A mount -t cifs -o username="$(whoami)",password="$(pass show default/windows)" "$chosen" "$mp" 2>/dev/null
+ else
+ notify-send "❌ Failed to mount samba" "$chosen
+Check if $(whoami) is added to samba user list."
+ sudo rm -rf "$mp"
+ exit
+ fi
+ notify-send "📡 Samba successfully mounted to" "$mp"
+ ;;
+esac
diff --git a/ar/.local/bin/mpdmenu b/ar/.local/bin/mpdmenu
new file mode 100755
index 0000000..4febe9d
--- /dev/null
+++ b/ar/.local/bin/mpdmenu
@@ -0,0 +1,111 @@
+#!/bin/bash
+
+all_name='[ALL]'
+PLAYLIST_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/mpd/playlists"
+
+# Functions
+d_artist() {
+ mpc list artist | sort -f | dmenu -p artist "${dmenu_args[@]}" || exit
+}
+
+d_album() {
+ local artist="$1"
+ local albums
+
+ mapfile -t albums < <(mpc list album artist "$artist")
+ if ((${#albums[@]} > 1)); then
+ {
+ printf '%s\n' "$all_name"
+ printf '%s\n' "${albums[@]}" | sort -f
+ } | dmenu -p album "${dmenu_args[@]}" || exit
+ else
+ # We only have one album, so just use that.
+ printf '%s\n' "${albums[0]}"
+ fi
+}
+
+d_title() {
+ titles=$(mpc list title | sort -f)
+ [[ -z "$titles" ]] && exit
+ echo "$titles" | dmenu -p "Select title:" "${dmenu_args[@]}"
+}
+
+d_playlist() {
+ playlists=$(find "$PLAYLIST_DIR" \( -type f -o -type l \) -name "*.m3u" -exec basename {} .m3u \;)
+
+ selected_playlist=$(echo "$playlists" | sort | dmenu -i -p "Select Playlist:" "${dmenu_args[@]}")
+
+ printf '%s' "$selected_playlist"
+}
+
+d_queue() {
+ format="%position% %title%"
+ extra_format="(%artist% - %album%)"
+
+ # If all tracks are from the same artist and album, no need to display that
+ num_extras=$(mpc playlist -f "$extra_format" | sort | uniq | wc -l)
+ ((num_extras == 1)) || format+=" $extra_format"
+
+ track=$(mpc playlist -f "$format" | dmenu -p track "${dmenu_args[@]}")
+ printf '%s' "${track%% *}"
+}
+
+# Parse arguments (if any)
+i=2
+for arg in "$@"; do
+ if [[ $arg == :: ]]; then
+ dmenu_args=("${@:$i}")
+ break
+ fi
+
+ case "$arg" in
+ -l) mode="library" ;;
+ -p) mode="playlist" ;;
+ -q) mode="queue" ;;
+ esac
+
+ ((i + 1))
+done
+
+# Main Menu
+[[ -z "$mode" ]] && mode=$(echo -e "library\nplaylist\nqueue" | dmenu -p "Choose mode:" "${dmenu_args[@]}")
+
+# Mode Handling
+case "$mode" in
+"library")
+ search_type=$(echo -e "artist\ntitle" | dmenu -p "Search by:" "${dmenu_args[@]}")
+ case "$search_type" in
+ "artist")
+ artist=$(d_artist)
+ album=$(d_album "$artist")
+ mpc clear
+ if [[ $album == "$all_name" ]]; then
+ mpc find artist "$artist" | sort | mpc add
+ else
+ mpc find artist "$artist" album "$album" | sort | mpc add
+ fi
+ ;;
+ "title")
+ title=$(d_title)
+ mpc clear
+ mpc find title "$title" | sort | mpc add
+ ;;
+ *) exit ;;
+ esac
+ mpc random on
+ mpc play 2>/dev/null
+ ;;
+"playlist")
+ mpc clear
+ mpc load "$(d_playlist)" 2>/dev/null || exit
+ mpc random on
+ mpc play 2>/dev/null
+ ;;
+"queue")
+ ! mpc playlist | grep -q '.' && exit
+ mpc play "$(d_queue)" 2>/dev/null || exit
+ ;;
+*) exit ;;
+esac
+
+sleep 0.5 && mpc volume 50
diff --git a/ar/.local/bin/mpvplay b/ar/.local/bin/mpvplay
new file mode 100755
index 0000000..967f47f
--- /dev/null
+++ b/ar/.local/bin/mpvplay
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+MOUNT_SCRIPT="${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/ecrypt"
+DB_PATH="$HOME/.local/share/history/mpv.sqlite"
+
+check_mount() { findmnt "$HOME/Private" >/dev/null || $MOUNT_SCRIPT; }
+
+check_unmount() { findmnt "$HOME/Private" >/dev/null && $MOUNT_SCRIPT; }
+
+play_url() {
+ url=$(xclip -selection clipboard -o)
+ [ -n "$url" ] && echo "$url" | grep -E '^https?://' >/dev/null || return 1
+ notify-send "📽️ Playing video from URL:" "$url"
+ mpv "$url" || exit
+}
+
+play_media() {
+ if echo "$1" | grep -q ".*\.m3u$"; then
+ playlist_file="${1#--playlist=}"
+ if grep -q "/home/$USER/Private" "$playlist_file"; then
+ mpv "$@" && check_unmount || exit
+ else
+ $MOUNT_SCRIPT && mpv "$@" || exit
+ fi
+ elif echo "$1" | grep -q "/home/$USER/Private"; then
+ mpv "$@" && check_unmount || exit
+ else
+ $MOUNT_SCRIPT && mpv "$@" || exit
+ fi
+}
+
+play_playlist() { play_media "--playlist=$1"; }
+
+tmp_playlist() {
+ playlistdir="$HOME/.config/mpv/playlists"
+ [ -d "$playlistdir" ] || mkdir -p "$playlistdir"
+ tmplist="$playlistdir/tmplist.m3u"
+ [ -f "$tmplist" ] && rm -rf "$tmplist"
+ find "$1" -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.mov" -o -iname "*.flv" -o -iname "*.wmv" -o -iname "*.webm" -o -iname "*.mpeg" -o -iname "*.mpg" -o -iname "*.avi" -o -iname "*.ts" -o -iname "*.3gp" -o -iname "*.rmvb" \) \
+ -exec echo {} \; >>"$tmplist"
+ play_playlist "$tmplist"
+ rm -rf "$tmplist"
+}
+
+list_and_play() {
+ dir=$1
+ CHOICE=$(printf "List files\nEnter filenames" | dmenu -i -p "Choose an option:")
+ case "$CHOICE" in
+ "Enter filenames")
+ search_term=$(echo | dmenu -i -p "File names:")
+ [ -z "$search_term" ] && echo "Invalid search term \"$search_term\"" && exit
+ notify-send "🔎 Finding videos named with '$search_term'.."
+ files=$(find "$dir" -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.mov" -o -iname "*.flv" -o -iname "*.wmv" -o -iname "*.webm" -o -iname "*.mpeg" -o -iname "*.mpg" -o -iname "*.avi" -o -iname "*.ts" -o -iname "*.3gp" -o -iname "*.rmvb" \) -iname "*$search_term*" | sort)
+ [ -z "$files" ] && echo "No files named with \"$search_term\"." && exit
+ tmpplaylist=$(mktemp /tmp/mpv_playlist_XXXXXX.m3u)
+ echo "$files" | while read -r file; do
+ echo "$file"
+ done >"$tmpplaylist"
+ play_playlist "$tmpplaylist"
+ rm -rf "$tmpplaylist"
+ ;;
+ "List files")
+ files_with_paths=$(find "$dir" -mindepth 1 -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.mov" -o -iname "*.flv" -o -iname "*.wmv" -o -iname "*.webm" -o -iname "*.mpeg" -o -iname "*.mpg" -o -iname "*.avi" -o -iname "*.ts" -o -iname "*.3gp" -o -iname "*.rmvb" \) | sort)
+ SELECTED_FILE=$(printf "All files\n%s" "$files_with_paths" | sed 's!.*/!!' | dmenu -i -l 21 -p "Select a file:")
+ [ -z "$SELECTED_FILE" ] && echo "No file selected." && exit
+ [ "$SELECTED_FILE" = "All files" ] && tmp_playlist "$dir" && return
+ FULL_PATH="$(echo "$files_with_paths" | grep -F "$SELECTED_FILE")"
+ [ -f "$FULL_PATH" ] && play_media "$FULL_PATH" && return
+ ;;
+ *) return ;;
+ esac
+}
+
+history_play() {
+ # Check if the database exists
+ if [ ! -f "$DB_PATH" ]; then
+ echo "Error: SQLite database not found at $DB_PATH" >&2
+ exit 1
+ fi
+
+ # Query the database for the latest distinct files by path, formatting time_pos as HH:MM:SS
+ HISTORY=$(
+ sqlite3 "$DB_PATH" <<EOF
+WITH LatestFiles AS (
+ SELECT path, title, time_pos, MAX(date) AS max_date
+ FROM loaded_items
+ GROUP BY path
+),
+FormattedHistory AS (
+ SELECT
+ path,
+ title,
+ CASE
+ WHEN time_pos IS NOT NULL THEN
+ printf('%02d:%02d:%02d',
+ time_pos / 3600,
+ (time_pos % 3600) / 60,
+ time_pos % 60
+ )
+ ELSE '00:00:00'
+ END AS formatted_time,
+ max_date
+ FROM LatestFiles
+)
+SELECT path || ' | ' || title || ' | ' || formatted_time
+FROM FormattedHistory
+ORDER BY max_date DESC;
+EOF
+ )
+
+ # Check if there are any results
+ if [ -z "$HISTORY" ]; then
+ echo "No history items found in the database." >&2
+ exit 1
+ fi
+
+ # Create a temporary file for filtered results
+ TEMP_FILE=$(mktemp)
+
+ # Filter out entries with non-existing files
+ echo "$HISTORY" | while IFS= read -r line; do
+ FILE_PATH=$(printf '%s\n' "$line" | awk -F ' \\| ' '{print $1}')
+ if [ -f "$FILE_PATH" ]; then
+ printf '%s\n' "$line" >>"$TEMP_FILE"
+ fi
+ done
+
+ # Check if there are valid entries after filtering
+ if [ ! -s "$TEMP_FILE" ]; then
+ echo "No valid history items found (all files missing)." >&2
+ rm -f "$TEMP_FILE"
+ exit 1
+ fi
+
+ # Display results in dmenu and get the user's choice
+ CHOSEN=$(dmenu -i -l 20 -p "Choose a file to play:" <"$TEMP_FILE")
+ rm -f "$TEMP_FILE"
+
+ # Check if the user made a selection
+ if [ -z "$CHOSEN" ]; then
+ echo "No file selected." >&2
+ exit 1
+ fi
+
+ # Extract the file path and formatted time position from the selected item
+ FILE_PATH=$(printf '%s\n' "$CHOSEN" | awk -F ' \\| ' '{print $1}')
+ FORMATTED_TIME=$(printf '%s\n' "$CHOSEN" | awk -F ' \\| ' '{print $3}')
+
+ # Convert the formatted time back to seconds for mpv
+ TIME_POS=$(printf '%s\n' "$FORMATTED_TIME" | awk -F: '{print ($1 * 3600) + ($2 * 60) + $3}')
+
+ # Play the file with mpv, resuming from the saved time position
+ if [ "$TIME_POS" -gt 0 ]; then
+ mpv --start="$TIME_POS" "$FILE_PATH"
+ else
+ mpv "$FILE_PATH"
+ fi
+}
+
+CONTENT_CHOICE=$(printf "URL\nLocal Files\nPlaylist\nHistory" | dmenu -i -p "Choose media source:")
+case "$CONTENT_CHOICE" in
+"URL") play_url ;;
+"Playlist")
+ PLAYLIST=$(find "$HOME/.config/mpv/playlists" -maxdepth 1 -type f -name "*.m3u" -exec basename {} .m3u \; | dmenu -i -p "Select a playlist:")
+ [ -z "$PLAYLIST" ] && exit
+ play_playlist "$HOME/.config/mpv/playlists/$PLAYLIST.m3u"
+ ;;
+"Local Files")
+ check_mount
+ printf "%s\n%s\n%s\n%s\n%s\n%s\n" "$HOME/Downloads" "$HOME/Private" "$HOME/Torrents/complete" "$HOME/Videos" "/media/$USER" "/mnt/second" | dmenu -i -p "Choose your initial directory:" | {
+ read -r init_dir
+ [ -z "$init_dir" ] && $MOUNT_SCRIPT && exit
+ SELECTED_DIR="$init_dir"
+ while true; do
+ SUBDIR_OPTIONS="$(find "$SELECTED_DIR" -mindepth 1 -maxdepth 1 -type d ! -name ".*" -printf "%P\n" | sort)"
+ [ -z "$SUBDIR_OPTIONS" ] && list_and_play "$SELECTED_DIR" && break
+ OPTIONS="All files\n$SUBDIR_OPTIONS"
+ SELECTED_RELATIVE_DIR="$(printf "%b" "$OPTIONS" | dmenu -i -p "Select a directory or 'All files':")"
+ [ -z "$SELECTED_RELATIVE_DIR" ] && echo "No relative directory." && exit
+ [ "$SELECTED_RELATIVE_DIR" = "All files" ] && list_and_play "$SELECTED_DIR" && break
+ SELECTED_DIR="$SELECTED_DIR/$SELECTED_RELATIVE_DIR"
+ done
+ }
+ ;;
+"History") history_play ;;
+*) exit ;;
+esac
+
+trap 'check_unmount; exit' EXIT INT
diff --git a/ar/.local/bin/noisereduce b/ar/.local/bin/noisereduce
new file mode 100755
index 0000000..709b257
--- /dev/null
+++ b/ar/.local/bin/noisereduce
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+usage() {
+ printf "Usage : noisereduce <input video file> <output video file>\n"
+ exit 1
+}
+
+# Tests for requirements
+ifinstalled ffmpeg || {
+ echo >&2 "We require 'ffmpeg' but it's not installed."
+ exit 1
+}
+ifinstalled sox || {
+ echo >&2 "We require 'ffmpeg' but it's not installed."
+ exit 1
+}
+
+if [ "$#" -ne 2 ]; then
+ usage
+fi
+
+if [ ! -e "$1" ]; then
+ printf "File not found: %s\n" "$1"
+ exit
+fi
+
+if [ -e "$2" ]; then
+ printf "File %s already exists, overwrite? [y/N]\n: " "$2"
+ read -r yn
+ case $yn in
+ [Yy]*) ;;
+ *) exit ;;
+ esac
+fi
+
+inBasename=$(basename "$1")
+inExt="${inBasename##*.}"
+
+isVideoStr=$(ffprobe -v warning -show_streams "$1" | grep codec_type=video)
+if [ -n "$isVideoStr" ]; then
+ isVideo=1
+ printf "Detected %s as a video file\n" "$inBasename"
+else
+ isVideo=0
+ printf "Detected %s as an audio file\n" "$inBasename"
+fi
+
+printf "Sample noise start time [00:00:00]: "
+read -r sampleStart
+if [ -z "$sampleStart" ]; then sampleStart="00:00:00"; fi
+printf "Sample noise end time [00:00:00.900]: "
+read -r sampleEnd
+if [ -z "$sampleEnd" ]; then sampleEnd="00:00:00.900"; fi
+printf "Noise reduction amount [0.21]: "
+read -r sensitivity
+if [ -z "$sensitivity" ]; then sensitivity="0.21"; fi
+
+tmpVidFile="/tmp/noiseclean_tmpvid.$inExt"
+tmpAudFile="/tmp/noiseclean_tmpaud.wav"
+noiseAudFile="/tmp/noiseclean_noiseaud.wav"
+noiseProfFile="/tmp/noiseclean_noise.prof"
+tmpAudCleanFile="/tmp/noiseclean_tmpaud-clean.wav"
+
+printf "Cleaning noise on %s...\n" "$1"
+
+if [ $isVideo -eq "1" ]; then
+ ffmpeg -v warning -y -i "$1" -qscale:v 0 -vcodec copy -an "$tmpVidFile"
+ ffmpeg -v warning -y -i "$1" -qscale:a 0 "$tmpAudFile"
+else
+ cp "$1" "$tmpAudFile"
+fi
+ffmpeg -v warning -y -i "$1" -vn -ss "$sampleStart" -t "$sampleEnd" "$noiseAudFile"
+sox "$noiseAudFile" -n noiseprof "$noiseProfFile"
+sox "$tmpAudFile" "$tmpAudCleanFile" noisered "$noiseProfFile" "$sensitivity"
+if [ $isVideo -eq "1" ]; then
+ ffmpeg -v warning -y -i "$tmpAudCleanFile" -i "$tmpVidFile" -vcodec copy -qscale:v 0 -qscale:a 0 "$2"
+else
+ cp "$tmpAudCleanFile" "$2"
+fi
+
+printf "Done"
diff --git a/ar/.local/bin/openfiles b/ar/.local/bin/openfiles
new file mode 100755
index 0000000..9aba756
--- /dev/null
+++ b/ar/.local/bin/openfiles
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+if ! command -v nvim >/dev/null 2>&1; then
+ echo "Error: 'nvim' is not installed." >&2
+ exit 1
+fi
+
+IFS='
+'
+
+files=$*
+
+for file in $files; do
+ files_list="$files_list \"$(realpath "$file")\""
+done
+
+eval "set -- $files_list"
+
+count=$#
+
+case "$count" in
+2)
+ ${EDITOR:-nvim} -O +'silent! normal g;' "$@"
+ ;;
+3)
+ ${EDITOR:-nvim} -O "$1" -c 'wincmd j' -c "silent! vsplit $2" -c "silent! split $3"
+ ;;
+4)
+ ${EDITOR:-nvim} -O "$1" -c "silent! vsplit $2" -c "silent! split $3" -c 'wincmd h' -c "silent! split $4"
+ ;;
+*)
+ ${EDITOR:-nvim} "$@"
+ ;;
+esac
diff --git a/ar/.local/bin/opensessions b/ar/.local/bin/opensessions
new file mode 100755
index 0000000..6f9f236
--- /dev/null
+++ b/ar/.local/bin/opensessions
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# split the selected directories into an array
+dirs="$*"
+
+# filter out any empty selections
+dirs=$(echo "$dirs" | tr -s ' ' '\n' | sed '/^$/d')
+[ -z "$dirs" ] && exit 0
+
+# function to clean and create a valid session name
+get_session_name() {
+ basename "$1" | sed 's/[^a-zA-Z0-9]/_/g'
+}
+
+set -- $dirs
+
+# handle session creation for multiple selected directories
+for dir in $dirs; do
+ if [ -d "$dir" ]; then
+ session_name=$(get_session_name "$dir")
+ if ! tmux has-session -t "$session_name" 2>/dev/null; then
+ tmux new-session -d -s "$session_name" -c "$dir"
+ if git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -n "$(git -C "$dir" status --porcelain)" ]; then
+ tmux send-keys -t "$session_name" "git status --porcelain" C-m
+ fi
+ fi
+ fi
+done
+
+if [ "$#" -gt 0 ]; then
+ first_session=$(get_session_name "$1")
+ if [ -n "$TMUX" ]; then
+ tmux switch-client -t "$first_session"
+ else
+ tmux attach-session -t "$first_session"
+ fi
+fi
diff --git a/ar/.local/bin/opentasktui b/ar/.local/bin/opentasktui
new file mode 100755
index 0000000..fa9ec5c
--- /dev/null
+++ b/ar/.local/bin/opentasktui
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Capture the current session name
+current_session=$(tmux display-message -p '#S')
+
+# Parse the task description from the argument
+TASK_DESCRIPTION="$1"
+
+# Create a new tmux session named 'new-task-session' and run 'tui -r current'
+tmux new-session -d -s new-task-session "taskwarrior-tui -r current"
+
+# Sleep for a bit to allow tui to load
+sleep 0.1
+
+# Send the keystrokes needed to filter tasks by description to the target pane
+tmux send-keys -t new-task-session:1.1 "/description:$TASK_DESCRIPTION" C-m
+
+# Attach to the new session
+tmux switch-client -t new-task-session
+
+# Wait for the session to be closed, either by the user or some other way
+while tmux has-session -t new-task-session 2>/dev/null; do
+ sleep 0.1
+done
+
+# Switch back to the original session
+tmux switch-client -t "$current_session"
diff --git a/ar/.local/bin/opout b/ar/.local/bin/opout
new file mode 100755
index 0000000..70bc2cb
--- /dev/null
+++ b/ar/.local/bin/opout
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# opout: "open output": A general handler for opening a file's intended output,
+# usually the pdf of a compiled document. I find this useful especially
+# running from vim.
+
+basename="${1%.*}"
+
+case "${*}" in
+*.tex | *.sil | *.m[dse] | *.[rR]md | *.mom | *.[0-9])
+ target="$(getcomproot "$1" || echo "$1")"
+ setsid -f xdg-open "${target%.*}".pdf >/dev/null 2>&1
+ ;;
+*.html) setsid -f "$BROWSER" "$basename".html >/dev/null 2>&1 ;;
+*.sent) setsid -f sent "$1" >/dev/null 2>&1 ;;
+esac
diff --git a/ar/.local/bin/otp b/ar/.local/bin/otp
new file mode 100755
index 0000000..be80265
--- /dev/null
+++ b/ar/.local/bin/otp
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# Get a one-time password, or add a OTP secret to your pass-otp store.
+
+# The assumption of this script is that all otp passwords are stored with the
+# suffix `-otp`. This script automatically appends newly added otps as such.
+
+# For OTP passwords to be generated properly, it is important for the local
+# computer to have its time properly synced. This can be done with the command
+# below which requires the package `ntp`.
+
+ifinstalled pass pass-otp || exit 1
+
+dir="${PASSWORD_STORE_DIR}"
+
+choice="$({
+ echo "🆕add"
+ echo "🕙sync-time"
+ ls "$dir"/*-otp.gpg
+} | sed "s/.*\///;s/-otp.gpg//" | dmenu -p "Pick a 2FA:")"
+
+case $choice in
+🆕add)
+ ifinstalled maim zbar || exit 1
+
+ temp=$(mktemp -p "$XDG_RUNTIME_DIR" --suffix=.png)
+ otp="otp-test-script"
+ trap 'rm -f $temp; pass rm -f $otp' HUP INT QUIT TERM PWR EXIT
+
+ notify-send "Scan the image." "Scan the OTP QR code."
+
+ maim -s "$temp" || exit 1
+ info="$(zbarimg -q "$temp")"
+ info="${info#QR-Code:}"
+
+ if echo "$info" | pass otp insert "$otp"; then
+ while true; do
+ export name="$(echo | dmenu -p "Give this One Time Password a one-word name:")"
+ echo "$name" | grep -q -- "^[A-z0-9-]\+$" && break
+ done
+ pass mv "$otp" "$name-otp"
+ notify-send "Successfully added." "$name-otp has been created."
+ else
+ notify-send "No OTP data found." "Try to scan the image again more precisely."
+ fi
+ ;;
+🕙sync-time)
+ ifinstalled ntp || exit 1
+ notify-send -u low "🕙 Synchronizing Time..." "Synching time with remote NTP servers..."
+ updatedata="$(sudo ntpdate pool.ntp.org)" &&
+ notify-send -u low "🕙 Synchronizing Time..." "Done. Time changed by ${updatedata#*offset }"
+ ;;
+*) pass otp -c ${choice}-otp ;;
+esac
diff --git a/ar/.local/bin/ovpn b/ar/.local/bin/ovpn
new file mode 100755
index 0000000..a89c357
--- /dev/null
+++ b/ar/.local/bin/ovpn
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+turnonoffvpn() {
+ case "$1" in
+ *on*)
+ nmcli connection up "$THESIAH_VPN" 2>/dev/null && notify-send "🛰️ THESIAH_VPN: ON" || notify-send "🛰️ Error to connect"
+ ;;
+ *off*)
+ nmcli connection down "$THESIAH_VPN" 2>/dev/null && notify-send "✂️ THESIAH_VPN: OFF" || notify-send "✂️ Error to disconnect"
+
+ ;;
+ esac
+}
+
+[ -n "$(cat /sys/class/net/tun*/operstate 2>/dev/null)" ] && {
+ turnonoffvpn off
+} || {
+ nmcli connection show | grep "$THESIAH_VPN" >/dev/null 2>&1 && turnonoffvpn on || {
+ nmcli connection import type openvpn file ~/.config/openvpn/thesiah.ovpn >/dev/null 2>&1
+ turnonoffvpn on
+ }
+}
diff --git a/ar/.local/bin/pacerror b/ar/.local/bin/pacerror
new file mode 100755
index 0000000..ec45002
--- /dev/null
+++ b/ar/.local/bin/pacerror
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Remove existing keyring and package database to start fresh
+sudo rm -rf /etc/pacman.d/gnupg /var/lib/pacman/sync
+
+# Reinitialize the pacman keyring
+sudo pacman-key --init
+
+# Install updated keyrings for Arch and Artix
+sudo pacman -Sy archlinux-keyring artix-keyring
+
+# Refresh all keys to ensure they're up-to-date
+sudo pacman-key --refresh-keys
+
+# Populate the keyring with default keys
+sudo pacman-key --populate archlinux artix
+
+# Clear the package cache
+sudo pacman -Scc --noconfirm
+
+# Forcefully refresh the package databases
+sudo pacman -Syy
+
+# Update the system packages
+sudo pacman -Syu
+
+remaps
diff --git a/ar/.local/bin/partlabel b/ar/.local/bin/partlabel
new file mode 100755
index 0000000..fb62fcd
--- /dev/null
+++ b/ar/.local/bin/partlabel
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+command_exists() { command -v "$1" >/dev/null 2>&1; }
+get_fs_type() { sudo blkid -o value -s TYPE "$1" 2>/dev/null; }
+label_ext() { sudo e2label "$1" "$2"; }
+label_fat() { sudo fatlabel "$1" "$2"; }
+label_ntfs() { sudo ntfslabel --force --quiet "$1" "$2"; }
+label_btrfs() { sudo btrfs filesystem label "$1" "$2"; }
+label_luks() { sudo cryptsetup config "$1" --label "$2"; }
+label_parted() { sudo parted "$1" name "$2" "$3"; }
+print_label() { sudo lsblk -o NAME,LABEL; }
+
+echo "Partition Labeling Script"
+for cmd in blkid e2label fatlabel ntfslabel btrfs parted lsblk cryptsetup; do
+ if ! command_exists "$cmd"; then
+ echo "Error: $cmd is not installed or not in PATH."
+ echo "Installing necessary package for $cmd..."
+ case $cmd in
+ blkid | lsblk)
+ sudo pacman -S --noconfirm util-linux
+ ;;
+ e2label)
+ sudo pacman -S --noconfirm e2fsprogs
+ ;;
+ fatlabel)
+ sudo pacman -S --noconfirm dosfstools
+ ;;
+ ntfslabel)
+ sudo pacman -S --noconfirm ntfs-3g
+ ;;
+ btrfs)
+ sudo pacman -S --noconfirm btrfs-progs
+ ;;
+ parted)
+ sudo pacman -S --noconfirm parted
+ ;;
+ cryptsetup)
+ sudo pacman -S --noconfirm cryptsetup
+ ;;
+ *)
+ echo "Unknown command: $cmd"
+ exit 1
+ ;;
+ esac
+ fi
+done
+
+print_label
+echo -n "\nEnter the partition (e.g., /dev/sda1): "
+read partition
+
+fs_type=$(get_fs_type "$partition")
+
+if [ -z "$fs_type" ]; then
+ echo "Unable to determine file system type for $partition. Please ensure the partition exists and has a valid file system."
+ echo "Debugging information:"
+ echo "blkid output for $partition:"
+ sudo blkid "$partition"
+ echo "Raw blkid output:"
+ sudo blkid
+ exit 1
+fi
+
+echo "Detected file system type: $fs_type"
+echo -n "Enter the label: "
+read label
+
+case "$fs_type" in
+ext2 | ext3 | ext4) label_ext "$partition" "$label" ;;
+vfat) label_fat "$partition" "$label" ;;
+ntfs) label_ntfs "$partition" "$label" ;;
+btrfs) label_btrfs "$partition" "$label" ;;
+crypto_LUKS)
+ echo "Labeling LUKS partition using cryptsetup."
+ label_luks "$partition" "$label"
+ ;;
+*)
+ echo "File system type $fs_type is not directly supported by this script. Attempting to label with parted."
+ label_parted "$partition" "$label"
+ ;;
+esac
+
+echo "Partition labeled successfully. Verifying..."
+print_label
+echo "Done."
diff --git a/ar/.local/bin/passmenu2 b/ar/.local/bin/passmenu2
new file mode 100755
index 0000000..00a6b35
--- /dev/null
+++ b/ar/.local/bin/passmenu2
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+shopt -s nullglob globstar
+
+typeit=0
+if [[ $1 == "--type" ]]; then
+ typeit=1
+ shift
+fi
+
+if [[ -n $WAYLAND_DISPLAY ]]; then
+ dmenu_cmd="dmenu-wl -l 20"
+ xdotool="ydotool type --file -"
+elif [[ -n $DISPLAY ]]; then
+ dmenu_cmd="dmenu -l 20"
+ xdotool="xdotool type --clearmodifiers --file -"
+else
+ echo "Error: No Wayland or X11 display detected" >&2
+ exit 1
+fi
+
+prefix=${PASSWORD_STORE_DIR-~/.password-store}
+password_files=("$prefix"/**/*.gpg)
+password_files=("${password_files[@]#"$prefix"/}")
+password_files=("${password_files[@]%.gpg}")
+
+password=$(printf '%s\n' "${password_files[@]}" | $dmenu_cmd "$@")
+
+[[ -n $password ]] || exit
+
+if [[ $typeit -eq 0 ]]; then
+ pass show -c "$password" 2>/dev/null
+else
+ pass show "$password" | {
+ IFS= read -r pass
+ printf %s "$pass"
+ } | $xdotool
+fi
diff --git a/ar/.local/bin/pauseallmpv b/ar/.local/bin/pauseallmpv
new file mode 100755
index 0000000..9b14148
--- /dev/null
+++ b/ar/.local/bin/pauseallmpv
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# You might notice all mpv commands are aliased to have this input-ipc-server
+# thing. That's just for this particular command, which allows us to pause
+# every single one of them with one command! This is bound to super + shift + p
+# (with other things) by default and is used in some other places.
+
+for i in $(ls /tmp/mpvSockets/*); do
+ echo '{ "command": ["set_property", "pause", true] }' | socat - "$i"
+done
diff --git a/ar/.local/bin/peertubetorrent b/ar/.local/bin/peertubetorrent
new file mode 100755
index 0000000..4d8f630
--- /dev/null
+++ b/ar/.local/bin/peertubetorrent
@@ -0,0 +1,9 @@
+#!/bin/sh
+# torrent peertube videos, requires the transadd script
+# first argument is the video link, second is the quality (360, 480 or 1080)
+# 13/07/20 - Arthur Bais
+
+instance=$(echo "$1" | sed "s|/w.\+||")
+vidid=$(echo "$1" | sed "s|.\+/||")
+link=$(curl -s "$instance/api/v1/videos/$vidid" | grep -o "$instance/download/torrents/.\{37\}$2.torrent")
+transadd "$link"
diff --git a/ar/.local/bin/podentr b/ar/.local/bin/podentr
new file mode 100755
index 0000000..ae72d41
--- /dev/null
+++ b/ar/.local/bin/podentr
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# entr command to run `queueandnotify` when newsboat queue is changed
+
+[ "$(pgrep -x "$(basename "$0")" | wc -l)" -gt 2 ] && exit
+
+echo "${XDG_DATA_HOME:-${HOME}/.local/share}"/newsboat/queue | entr -p queueandnotify 2>/dev/null
diff --git a/ar/.local/bin/qndl b/ar/.local/bin/qndl
new file mode 100755
index 0000000..c9c1be2
--- /dev/null
+++ b/ar/.local/bin/qndl
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+YTDL_CMD_BASE="yt-dlp --continue --embed-metadata --ignore-errors --no-force-overwrites --no-playlist --verbose"
+
+# Process command-line options for download type
+while getopts "mvr" opt; do
+ case $opt in
+ m)
+ DOWNLOAD_TYPE="music"
+ OUTPUT_DIR="${XDG_MUSIC_DIR:-${HOME}/Music}"
+ ARCHIVE_FILE="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/default/Music/.music.txt"
+ YTDL_OUTPUT_FORMAT="${OUTPUT_DIR}/%(artist|)s%(artist& - |)s%(title)s.%(ext)s"
+ YTDL_CMD_BASE="$YTDL_CMD_BASE --audio-format best --audio-quality 0 --download-archive \"$ARCHIVE_FILE\" --extract-audio --recode-video mp3"
+ ;;
+ v)
+ DOWNLOAD_TYPE="video"
+ OUTPUT_DIR="${XDG_VIDEOS_DIR:-${HOME}/Videos}"
+ YTDL_OUTPUT_FORMAT="${OUTPUT_DIR}/%(title)s [%(id)s].%(ext)s"
+ VIDEO_EXT=$(printf "best\n60fps\n30fps\nmp4\nmkv" | dmenu -i -p "Choose an encoding (default: 1080p)") || exit
+ case $VIDEO_EXT in
+ best)
+ VIDEO_FORMATS="--format bestvideo+bestaudio/best"
+ VIDEO_OPTIONS=""
+ ;;
+ 60fps)
+ VIDEO_FORMATS='--format "((bv*[fps=60]/bv*)[height<=1080]/(wv*[fps=60]/wv*)) + ba / (b[fps=60]/b)[height<=1080]/(w[fps=60]/w)"'
+ VIDEO_OPTIONS=""
+ ;;
+ 30fps)
+ VIDEO_FORMATS='--format "((bv*[fps=30]/bv*)[height<=1080]/(wv*[fps=30]/wv*)) + ba / (b[fps=30]/b)[height<=1080]/(w[fps=30]/w)"'
+ VIDEO_OPTIONS=""
+ ;;
+ *)
+ VIDEO_FORMATS="--format bestvideo+bestaudio/best"
+ VIDEO_OPTIONS="--recode-video $VIDEO_EXT"
+ ;;
+ esac
+ YTDL_CMD_BASE="$YTDL_CMD_BASE --buffer-size 1M --embed-thumbnail $VIDEO_FORMATS --no-sponsorblock $VIDEO_OPTIONS"
+ ;;
+ r)
+ OUTPUT_DIR="${XDG_MUSIC_DIR:-${HOME}/Music}"
+ ARCHIVE_FILE="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/default/Music/.music.txt"
+ YTDL_OUTPUT_FORMAT="${OUTPUT_DIR}/%(artist|)s%(artist& - |)s%(title)s.%(ext)s"
+ YTDL_CMD_BASE="$YTDL_CMD_BASE --audio-format best --audio-quality 0 --extract-audio --recode-video mp3"
+ YTDL_CMD="$YTDL_CMD_BASE --output \"$YTDL_OUTPUT_FORMAT\""
+ [ ! -f "$ARCHIVE_FILE" ] && exit 1
+ while read -r line; do
+ video_id=$(echo "$line" | awk '{print $2}')
+ YTDL_CMD="$YTDL_CMD_BASE --output \"$YTDL_OUTPUT_FORMAT\" \"https://www.youtube.com/watch?v=$video_id\""
+ idnum=$(tsp bash -c "$YTDL_CMD")
+ pkill -RTMIN+21 "${STATUSBAR:-dwmblocks}"
+ done <"$ARCHIVE_FILE"
+ exit 0
+ ;;
+ *)
+ notify-send "⛔ Invalid option: -$OPTARG"
+ exit 1
+ ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+# Use the first non-option argument as the URL if provided, else from clipboard
+URL="${1:-$(xclip -selection clipboard -o)}"
+[ -z "$URL" ] && notify-send "⛔ No URL provided and clipboard is empty or does not contain a valid URL." && exit 1
+
+# Validate the URL format
+! echo "$URL" | grep -qE '^https?://[a-zA-Z0-9.-]+(/[a-zA-Z0-9./?&%=_-]*)?$' && notify-send "⛔ Invalid URL format: $URL" && exit 1
+
+# Validate URL accessibility
+! curl --head --silent --fail "$URL" >/dev/null && notify-send "⛔ URL is not accessible: $URL" && exit 1
+
+case $URL in
+*playlist* | *list=*)
+ PL_DOWNLOAD_CHOICE=$(printf "playlist\na content" | dmenu -i -p "Download entire playlist or just this content?")
+ [ "$PL_DOWNLOAD_CHOICE" = "playlist" ] &&
+ YTDL_CMD_BASE=$(echo "$YTDL_CMD_BASE" | sed 's/ --no-playlist//') &&
+ YTDL_CMD_BASE="$YTDL_CMD_BASE --yes-playlist" &&
+ echo 🪏 >/tmp/qplaylist
+ [ "$DOWNLOAD_TYPE" = "video" ] &&
+ SUBDIR=$(yt-dlp --dump-single-json "$URL" --no-playlist | jq -r '.channel' | sed 's/[\/:*?"<>|]/_/g;s/[[:space:]]/-/g') &&
+ mkdir -p "${OUTPUT_DIR}/${SUBDIR}" &&
+ YTDL_OUTPUT_FORMAT="${OUTPUT_DIR}/${SUBDIR}/%(playlist_index)02d_%(title)s [%(id)s].%(ext)s"
+ ;;
+esac
+
+SIMULATION_CMD="yt-dlp --simulate --print filename $URL"
+YTDL_CMD="$YTDL_CMD_BASE --output \"$YTDL_OUTPUT_FORMAT\" \"$URL\""
+
+# Notify and perform simulation to get filename (feedback to user)
+echo "$SIMULATION_CMD" | while IFS= read -r line; do
+ filename=$(basename "$line")
+ notify-send "📥 Queuing $DOWNLOAD_TYPE to download:" "$filename"
+done
+
+# Enqueue the download task with tsp
+FILENAME=$($SIMULATION_CMD 2>/dev/null)
+rm -rf /tmp/qplaylist
+notify-send "⏳ Downloading $DOWNLOAD_TYPE:" "$FILENAME"
+idnum=$(tsp bash -c "$YTDL_CMD")
+pkill -RTMIN+21 "${STATUSBAR:-dwmblocks}"
+
+# Notify upon completion
+tsp -D "$idnum" notify-send "✅ $DOWNLOAD_TYPE download complete:" "$URL"
+
+# Conditionally update the music database if the download type is music
+[ "$DOWNLOAD_TYPE" = "music" ] && tsp -D "$idnum" bash -c "mpc update"
diff --git a/ar/.local/bin/queueandnotify b/ar/.local/bin/queueandnotify
new file mode 100755
index 0000000..0059e93
--- /dev/null
+++ b/ar/.local/bin/queueandnotify
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# Podboat sucks. This script replaces it.
+# It reads the newsboat queue, queuing downloads with taskspooler.
+# It also removes the junk from extensions.
+queuefile="${XDG_DATA_HOME:-${HOME}/.local/share}/newsboat/queue"
+
+while read -r line; do
+ [ -z "$line" ] && continue
+ url="${line%%[ ]*}"
+ qndl "$url" "curl -LO"
+done <"$queuefile"
+
+echo >"$queuefile"
diff --git a/ar/.local/bin/rbackup b/ar/.local/bin/rbackup
new file mode 100755
index 0000000..c78c029
--- /dev/null
+++ b/ar/.local/bin/rbackup
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+# local backup
+backup_path="/mnt/second/backup"
+dot_path="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}"
+git_path="$HOME/Private/repos"
+pass_path="${PASSWORD_STORE_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/.password-store}/exported_keys"
+suck_path="${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless"
+USER_HOME=$(eval echo ~"$USER")
+
+# targets
+bash_path="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/ar/.config/bash"
+shell_path="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/ar/.config/shell"
+vim_path="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/ar/.config/vim"
+thesiah_path="${THESIAH_WWW:-${HOME}/Private/repos/THESIAH}/public"
+lf_path="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}/ar/.config/lf"
+
+usage() {
+ echo "Synchronize files and save them in backup path."
+ echo ""
+ echo "Usage: ${0##*/} [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " -h, --help Show this help message"
+ echo " -r, --root Sync root files only"
+ echo ""
+ echo "Example:"
+ echo " ${0##*/} # Sync all files to backup path"
+ echo " ${0##*/} --root # Sync root files only"
+ exit 0
+}
+
+error() {
+ printf "%s\n" "$1" >&2
+ exit 1
+}
+
+mount_luks() {
+ if ! mount | grep -q " /mnt/second "; then
+ SIZE_NVME0=$(sudo blockdev --getsize64 /dev/nvme0n1p1)
+ SIZE_NVME1=$(sudo blockdev --getsize64 /dev/nvme1n1p1)
+ [ "$SIZE_NVME1" -lt "$SIZE_NVME0" ] && TARGET_DEVICE="/dev/nvme0n1p1" || TARGET_DEVICE="/dev/nvme1n1p1"
+ ${TERMINAL:-st} -n floatterm -g 60x1 -e sudo cryptsetup open "$TARGET_DEVICE" "second"
+ sudo -A mount "/dev/mapper/second" "/mnt/second" -o uid="$(id -u)",gid="$(id -g)" 2>/dev/null || sudo -A mount "/dev/mapper/second" "/mnt/second"
+ fi
+}
+
+# Using a loop over space-separated strings instead of an array
+sync_files() {
+ for source in "$dot_path" "$git_path" "$pass_path" "$suck_path"; do
+ rsync -vrazPlu --exclude=".music.txt" --delete "$source" "$backup_path/" >/dev/null 2>&1 || {
+ echo "Failed to sync $(basename "$source")"
+ }
+ done
+}
+
+sync_root() {
+ # clean targets
+ sudo rm -rf /root/.config /root/.bash_history /root/.local/share/history
+ sudo mkdir -p /root/.config/bash /root/.config/lf /root/.config/shell /root/.config/vim /root/.local/bin /root/.local/share/history/vim_history /root/.local/state
+
+ # Root configuration synchronization on local system
+ sudo rsync -vrazPlu --delete "$vim_path/vimrc" "/root/.config/vim/" >/dev/null 2>&1
+ sudo rsync -vrazPlu --delete "$lf_path" "/root/.config/" >/dev/null 2>&1
+ sudo mv -f "/root/.config/lf/rooticons" "/root/.config/lf/icons" >/dev/null 2>&1
+ sudo rsync -vrazPlu --delete "$bash_path" "/root/.config/" >/dev/null 2>&1
+ sudo rsync -vrazPlu --delete "$shell_path/inputrc" "/root/.config/shell/" >/dev/null 2>&1
+
+ # load shortcuts
+ shortcuts >/dev/null 2>&1
+
+ # Modify root's Bash and LF configuration to include user-specific settings
+ echo "[ -f \"$USER_HOME/.config/shell/shortcutrc\" ] && source \"$USER_HOME/.config/shell/shortcutrc\"" | sudo tee -a /root/.config/bash/bashrc >/dev/null 2>&1
+ echo "[ -f \"$USER_HOME/.config/shell/zshnameddirrc\" ] && source \"$USER_HOME/.config/shell/zshnameddirrc\"" | sudo tee -a /root/.config/bash/bashrc >/dev/null 2>&1
+ sudo sed -i "s|source[[:space:]]*\"\?~/.config/lf/shortcutrc\"\?|source \"$USER_HOME/.config/lf/shortcutrc\"|" /root/.config/lf/lfrc >/dev/null 2>&1
+ sudo grep -q "source \"\?/root/.config/lf/rootshortcutrc\"\?" /root/.config/lf/lfrc ||
+ sudo sed -i "\|source \"\?$USER_HOME/.config/lf/shortcutrc\"\?|a source \"/root/.config/lf/rootshortcutrc\"" /root/.config/lf/lfrc
+
+ # Final ownership and link adjustments
+ sudo chown -R root:root /root/.config/ >/dev/null 2>&1
+ sudo ln -sf /root/.config/bash/bashrc /root/.bashrc >/dev/null 2>&1
+ sudo ln -sf /root/.config/bash/bash_profile /root/.bash_profile >/dev/null 2>&1
+ sudo ln -sf /root/.config/shell/inputrc /root/.inputrc >/dev/null 2>&1
+ sudo ln -sf /root/.config/vim/vimrc /root/.vimrc >/dev/null 2>&1
+}
+
+sync_server() {
+ # clean targets
+ ssh "$THESIAH_SERVER" "rm -rf /root/.config /var/www/thesiah"
+ ssh "$THESIAH_SERVER" "mkdir -p /root/.config/bash /root/.config/shell /root/.config/vim /root/.local/bin /root/.local/share /root/.local/state /var/www/thesiah"
+
+ # Sync operations with explicit error checking
+ rsync -vrazPlu --delete "$thesiah_path/" "$THESIAH_SERVER:/var/www/thesiah/" >/dev/null 2>&1
+ rsync -vrazPlu --delete "$vim_path/vimrc" "$THESIAH_SERVER:/root/.config/vim/" >/dev/null 2>&1
+ rsync -vrazPlu --delete "$shell_path/inputrc" "$THESIAH_SERVER:/root/.config/shell/" >/dev/null 2>&1
+ sudo cp /root/.config/shell/rootshortcutrc ~/.cache/
+ sudo chown -R "$USER":wheel ~/.cache/rootshortcutrc
+ rsync -vrazPlu --remove-source-files "$HOME/.cache/rootshortcutrc" "$THESIAH_SERVER:/root/.config/shell/" >/dev/null 2>&1
+
+ # Adding custom shortcuts to root's shell configuration on the remote system
+ ssh "$THESIAH_SERVER" "echo 'web=\"cd /var/www && ls -A\" \\' >> /root/.config/shell/rootshortcutrc"
+ ssh "$THESIAH_SERVER" "echo 'wen=\"cd /var/www/nextcloud && ls -A\" \\' >> /root/.config/shell/rootshortcutrc"
+ ssh "$THESIAH_SERVER" "echo 'wep=\"cd /var/www/prosody && ls -A\" \\' >> /root/.config/shell/rootshortcutrc"
+ ssh "$THESIAH_SERVER" "echo 'wet=\"cd /var/www/thesiah && ls -A\" \\' >> /root/.config/shell/rootshortcutrc"
+ ssh "$THESIAH_SERVER" "echo 'gng=\"cd /etc/nginx/sites-available && ls -A\" \\' >> /root/.config/shell/rootshortcutrc"
+
+ # Sync Bash configuration
+ rsync -vrazPlu --delete "$bash_path" "$THESIAH_SERVER:/root/.config/" >/dev/null 2>&1
+ ssh "$THESIAH_SERVER" "chown -R root:root /var/www/thesiah"
+ ssh "$THESIAH_SERVER" "chown -R root:root /root/.config/"
+ ssh "$THESIAH_SERVER" "ln -sf /root/.config/bash/bash_profile /root/.profile"
+ ssh "$THESIAH_SERVER" "source /root/.profile"
+
+ # Sync for Git
+ ssh "$THESIAH_SERVER" "cp -r /root/.config /var/www/git/"
+ ssh "$THESIAH_SERVER" "chown -R git:git /var/www/git/.config/"
+ ssh "$THESIAH_GIT" "ln -sf /var/www/git/.config/bash/bash_profile /var/www/git/.profile"
+ ssh "$THESIAH_GIT" "source /var/www/git/.profile"
+}
+
+sync_nextcloud() {
+ base="$(basename $backup_path)"
+ parent="$(dirname $backup_path)"
+ tmpdir="$(mktemp -d)"
+ cd "$tmpdir" || exit
+ tar -C "$parent" -zcf "$base".tar.gz "$base" >/dev/null 2>&1
+ rsync -vrazPlu --delete "$tmpdir/$base".tar.gz "$THESIAH_SERVER:/var/www/nextcloud/data/si@thesiah.xyz/files/backup/" >/dev/null 2>&1
+ ssh "$THESIAH_SERVER" "chown -R www-data:www-data /var/www/nextcloud/data/si@thesiah.xyz/files/backup" >/dev/null 2>&1
+ ssh "$THESIAH_SERVER" "cd /var/www/nextcloud && sudo -u www-data ./occ files:scan --path="/si@thesiah.xyz/files"" >/dev/null 2>&1
+ rm -r "$tmpdir"
+}
+
+handle_long_option() {
+ case $1 in
+ help)
+ usage
+ ;;
+ root)
+ echo "Sync root files..."
+ sync_root && echo "Success to sync root!" && echo "Done!" || error "Failed to back up root"
+ exit 0
+ ;;
+ *)
+ error "Unknown option: --$1"
+ ;;
+ esac
+}
+
+process_options() {
+ while [ $# -gt 0 ]; do
+ case $1 in
+ -h | --help)
+ usage
+ ;;
+ -r | --root)
+ echo "Sync root files..."
+ sync_root && echo "Success to sync root!" && echo "Done!" || error "Failed to back up root"
+ exit 0
+ ;;
+ --*)
+ handle_long_option "${1#--}"
+ ;;
+ -*)
+ error "Unknown option: $1"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+}
+
+# Start script
+echo "Backup starts to $backup_path..."
+process_options "$@"
+
+# Main script logic
+mount_luks && echo "Mount backup drive... " && echo "Success to mount luks drive!" || error "Failed to mount $backup_path"
+[ -d "$backup_path" ] || sudo mkdir -p "$backup_path"
+echo "Sync home files..." && sync_files && echo "Success to sync files!" || error "Failed back up files"
+echo "Sync root files..." && sync_root && echo "Success to sync root!" || error "Failed back up root"
+echo "Sync server files..." && sync_server && echo "Success to sync server!" || error "Failed back up server"
+echo "Sync files to nextcloud..." && sync_nextcloud && echo "Success to sync nextcloud!" || error "Failed back up nextcloud"
+echo "Done!"
diff --git a/ar/.local/bin/remapd b/ar/.local/bin/remapd
new file mode 100755
index 0000000..f669b0c
--- /dev/null
+++ b/ar/.local/bin/remapd
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Rerun the remaps script whenever a new input device is added.
+
+while :; do
+ remaps
+ grep -qP -m1 '[^un]bind.+\/[^:]+\(usb\)' <(udevadm monitor -u -t seat -s input -s usb)
+done
diff --git a/ar/.local/bin/remaps b/ar/.local/bin/remaps
new file mode 100755
index 0000000..153ce29
--- /dev/null
+++ b/ar/.local/bin/remaps
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# This script is called on startup to remap keys.
+# Decrease key repeat delay to 300ms and increase key repeat rate to 50 per second.
+xset r rate 200 50
+# Map the caps lock key to control, and map the menu key to right super.
+xinput list | grep 'id=' | while read -r line; do
+ id=$(echo "$line" | grep -i 'keyboard.*id.*keyboard' | sed 's/.*id=\([0-9]\+\).*/\1/')
+ [ -z "$id" ] || {
+ case "$(echo "$line" | grep -oE '.*id=' | sed 's/ id=.*//')" in
+ *"Lite-On Tech Lenovo USB Travel Keyboard with Ultra Nav"*)
+ setxkbmap -device "$id" -option
+ setxkbmap -device "$id" -option caps:ctrl_modifier,ctrl:swap_lwin_lctl
+ ;;
+ *"Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint"*)
+ setxkbmap -device "$id" -option
+ setxkbmap -device "$id" -option caps:ctrl_modifier,altwin:menu_win,altwin:swap_lalt_lwin
+ ;;
+ *"AT Translated Set 2 keyboard"*)
+ setxkbmap -device "$id" -option
+ setxkbmap -device "$id" -option caps:ctrl_modifier,altwin:menu_win,altwin:swap_lalt_lwin
+ ;;
+ *"Soomin Im’s Keyboard"*)
+ setxkbmap -device "$id" -option
+ setxkbmap -device "$id" -option caps:ctrl_modifier
+ ;;
+ *"HHKB"*)
+ setxkbmap -device "$id" -option
+ setxkbmap -device "$id" -option altwin:menu_win
+ ;;
+ *)
+ setxkbmap -device "$id" -option
+ setxkbmap -device "$id" -option caps:ctrl_modifier,altwin:menu_win
+ ;;
+ esac
+ }
+ id=$(echo "$line" | grep -i '.*id.*pointer' | sed 's/.*id=\([0-9]\+\).*/\1/')
+ [ -z "$id" ] || {
+ case "$(echo "$line" | grep -oE '.*id=' | sed 's/ id=.*//')" in
+ *"Apple Inc. Magic Trackpad"*)
+ xinput set-prop "$id" "libinput Tapping Enabled" 0
+ ;;
+ *"SynPS/2 Synaptics TouchPad"*)
+ xinput set-prop "$id" "libinput Tapping Enabled" 0
+ ;;
+ *"Lite-On Tech Lenovo USB Travel Keyboard with Ultra Nav Mouse"*)
+ [ -z "$1" ] && xinput set-prop "$id" "Coordinate Transformation Matrix" 5, 0, 0, 0, 5, 0, 0, 0, 1 || xinput set-prop "$id" "Coordinate Transformation Matrix" $1, 0, 0, 0, $1, 0, 0, 0, 1
+ xinput set-prop "$id" "libinput Scroll Method Enabled" 0, 0, 1
+ ;;
+ *"Logitech USB Receiver"*)
+ [ -z "$1" ] && xinput set-prop "$id" "Coordinate Transformation Matrix" 3, 0, 0, 0, 3, 0, 0, 0, 1 || xinput set-prop "$id" "Coordinate Transformation Matrix" $1, 0, 0, 0, $1, 0, 0, 0, 1
+ ;;
+ *"TPPS/2 IBM TrackPoint"*)
+ [ -z "$1" ] && xinput set-prop "$id" "Coordinate Transformation Matrix" 1, 0, 0, 0, 1, 0, 0, 0, 1 || xinput set-prop "$id" "Coordinate Transformation Matrix" $1, 0, 0, 0, $1, 0, 0, 0, 1
+ ;;
+ *"Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint"*)
+ [ -z "$1" ] && xinput set-prop "$id" "Coordinate Transformation Matrix" 3, 0, 0, 0, 3, 0, 0, 0, 1 || xinput set-prop "$id" "Coordinate Transformation Matrix" $1, 0, 0, 0, $1, 0, 0, 0, 1
+ ;;
+ esac
+ }
+done
+# When left control, caps lock, or Super_L is pressed only once, treat it as escape.
+killall xcape 2>/dev/null
+xcape -e 'Caps_Lock=Escape' #;Control_L=Escape' #;Super_L=Escape'
+# Turn off caps lock if on since there is no longer a key for it.
+xset -q | grep -q "Caps Lock:\s*on" && xdotool key Caps_Lock
diff --git a/ar/.local/bin/restartnvim b/ar/.local/bin/restartnvim
new file mode 100755
index 0000000..74d57c3
--- /dev/null
+++ b/ar/.local/bin/restartnvim
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+set -e
+
+# Set new line and tab for word splitting
+IFS="
+ "
+
+# Check if the script is running inside a tmux session
+if [ -z "$TMUX" ]; then
+ echo "This script must be run from inside a tmux session."
+ exit 1
+fi
+
+# Get the current tmux pane ID
+TMUX_PANE=$(tmux display-message -p '#D')
+
+# Send Escape, :wq, and Enter to Neovim in the tmux pane
+tmux send-keys -t "$TMUX_PANE" Escape C-m ':wq' C-m
+
+# Wait to ensure Neovim exits
+sleep 0.5
+
+# Detach the script from Neovim and wait a bit to ensure Neovim exits
+(nohup sh -c "sleep 0.5; tmux send-keys -t \"$TMUX_PANE\" 'nvim -c \"execute \\\"edit \\\" . v:oldfiles[0] | normal '\''0\"' C-m" >/dev/null 2>&1 &)
diff --git a/ar/.local/bin/rgafiles b/ar/.local/bin/rgafiles
new file mode 100755
index 0000000..6088933
--- /dev/null
+++ b/ar/.local/bin/rgafiles
@@ -0,0 +1,120 @@
+#!/bin/sh
+
+# Usage function to display script options
+usage() {
+ echo "Find files using ripgrep and open them in Neovim."
+ echo ""
+ echo "Usage: ${0##*/} [-s] [-i] [-l] [-p] [<tag>] <query>"
+ echo ""
+ echo "Options:"
+ echo " -h : Show this message"
+ echo " -i : Perform a case-insensitive search (default)"
+ echo " -l : List files associated with the given tag"
+ echo " -p : Search for files in the specified project directories using the specified tag (default: PROJECT)"
+ echo " -s : Perform a case-sensitive search"
+ echo " [<tag>] <query> : Optional tag for project mode, followed by the search query"
+ echo ""
+ echo "Examples:"
+ echo " ${0##*/} -p TODO 'KEYWORD' # Search for 'KEYWORD' in files tagged with 'TODO' in the project directories"
+ echo " ${0##*/} -l -p 'KEYWORD' # List files associated with the default 'PROJECT' tag and 'KEYWORD'"
+ echo " ${0##*/} 'KEYWORD' # Open files containing 'KEYWORD' in nvim"
+ exit 0
+}
+
+search_term() {
+ ignore_case_flag="$1"
+ shift
+
+ if ! command -v rga >/dev/null 2>&1; then
+ echo "Error: 'rga' is not installed." >&2
+ exit 1
+ fi
+ if ! command -v xclip >/dev/null 2>&1; then
+ echo "Error: 'xclip' is not installed." >&2
+ exit 1
+ fi
+
+ # Construct the preview command
+ preview_cmd=$(printf "rga %s --pretty --context 10 '%s' {}" "$ignore_case_flag" "$*")
+ rga_output=$(rga --follow --no-ignore --hidden --text --max-count=1 ${ignore_case_flag:+$ignore_case_flag} --files-with-matches --no-messages --glob '!**/.git/*' "$*")
+
+ # Use fzf to select files
+ files=$(echo "$rga_output" | fzf-tmux +m --preview="$preview_cmd" --reverse --multi --select-1 --exit-0) || return 1
+
+ # Check if files are selected
+ if [ -z "$files" ]; then
+ echo "No files selected."
+ return 0
+ fi
+
+ # copy target to the clipboard
+ echo "$@" | xclip -selection clipboard 2>/dev/null
+
+ openfiles "$files"
+
+ # print the file names
+ echo "$rga_output"
+}
+
+# Function to list and/or open all files associated with a given project tag
+list_or_open_project_files() {
+ # Use the provided tag or default to "PROJECT"
+ project_tag="${1:-PROJECT}: $2"
+
+ # Define the project paths as a space-separated string
+ project_paths="$HOME/.dotfiles $HOME/.local/src/suckless $HOME/Public/repos"
+
+ # Use rga to find files containing the project tag across all project paths
+ rga_output=""
+ for path in $project_paths; do
+ if [ -d "$path" ]; then
+ rga_result=$(rga --follow --no-ignore --hidden --text --max-count=1 --files-with-matches --no-messages --glob '!**/.git/*' "$project_tag" "$path")
+ rga_output="$rga_output $rga_result"
+ fi
+ done
+
+ # Remove leading/trailing whitespace
+ rga_output=$(echo "$rga_output" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+
+ # Check if any files were found
+ if [ -z "$rga_output" ]; then
+ echo "No files found for tag $project_tag."
+ return 0
+ fi
+
+ # If the script was called in list mode, simply print the files
+ if [ "$list_mode" -eq 1 ]; then
+ echo "$rga_output"
+ else
+ # Otherwise, open the files with nvim
+ set -- "$(printf "%s\n" "$rga_output")"
+ openfiles "$@"
+ fi
+}
+
+# Main function to handle options
+ignore_case_flag="--ignore-case" # Default to case-insensitive
+list_mode=0
+project_mode=0
+
+# Parse the options
+while getopts "silph" opt; do
+ case $opt in
+ s) ignore_case_flag="" ;; # Case-sensitive
+ i) ignore_case_flag="--ignore-case" ;; # Case-insensitive
+ l) list_mode=1 ;; # List mode
+ p) project_mode=1 ;; # Project mode
+ h) usage ;;
+ *) ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+# Handle project mode search
+if [ "$project_mode" -eq 1 ]; then
+ list_or_open_project_files "$1" "$2"
+else
+ # Otherwise, call the common search function
+ search_term "$ignore_case_flag" "$@"
+fi
diff --git a/ar/.local/bin/rotdir b/ar/.local/bin/rotdir
new file mode 100755
index 0000000..d171f29
--- /dev/null
+++ b/ar/.local/bin/rotdir
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# When I open an image from the file manager in nsxiv (the image viewer), I want
+# to be able to press the next/previous keys to key through the rest of the
+# images in the same directory. This script "rotates" the content of a
+# directory based on the first chosen file, so that if I open the 15th image,
+# if I press next, it will go to the 16th etc. Autistic, I know, but this is
+# one of the reasons that nsxiv is great for being able to read standard input.
+
+[ -z "$1" ] && echo "usage: rotdir regex 2>&1" && exit 1
+base="$(basename "$1")"
+ls "$PWD" | awk -v BASE="$base" 'BEGIN { lines = ""; m = 0; } { if ($0 == BASE) { m = 1; } } { if (!m) { if (lines) { lines = lines"\n"; } lines = lines""$0; } else { print $0; } } END { print lines; }'
diff --git a/ar/.local/bin/rssadd b/ar/.local/bin/rssadd
new file mode 100755
index 0000000..9e68650
--- /dev/null
+++ b/ar/.local/bin/rssadd
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if echo "$1" | grep -q "https*://\S\+\.[A-Za-z]\+\S*"; then
+ url="$1"
+else
+ url="$(grep -Eom1 '<[^>]+(rel="self"|application/[a-z]+\+xml)[^>]+>' "$1" |
+ grep -o "https?://[^\" ]")"
+
+ echo "$url" | grep -q "https*://\S\+\.[A-Za-z]\+\S*" ||
+ notify-send "That doesn't look like a full URL." && exit 1
+fi
+
+RSSFILE="${XDG_CONFIG_HOME:-${HOME}/.config}/newsboat/urls"
+if awk '{print $1}' "$RSSFILE" | grep "^$url$" >/dev/null; then
+ notify-send "You already have this RSS feed."
+else
+ echo "$url" >>"$RSSFILE" && notify-send "RSS feed added."
+fi
diff --git a/ar/.local/bin/schedule b/ar/.local/bin/schedule
new file mode 100755
index 0000000..c339e2b
--- /dev/null
+++ b/ar/.local/bin/schedule
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+if [ -z "$1" ]; then
+ echo "Usage: ${0##*/} <date-string>"
+ echo " Example: ${0##*/} 'tomorrow 06:00'"
+ exit 2
+fi
+
+set -e
+
+# Calculate the seconds until the given date
+secsUntil=$(expr "$(date +%s -d "$*")" - "$(date +%s)")
+
+# Calculate minutes and hours
+minutesUntil=$(echo "scale=1; $secsUntil/60" | bc)
+hoursUntil=$(echo "scale=2; $secsUntil/3600" | bc)
+
+# Get the formatted date
+date=$(date -d "$*")
+
+# Display the result
+echo "$hoursUntil hours (or $minutesUntil mins) until $date"
diff --git a/ar/.local/bin/screenshotactivewindow b/ar/.local/bin/screenshotactivewindow
new file mode 100755
index 0000000..79365e0
--- /dev/null
+++ b/ar/.local/bin/screenshotactivewindow
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -eo pipefail
+
+pidfile=/tmp/screencast.lock
+
+if [ -e $pidfile ]; then
+ pid=$(cat $pidfile)
+ echo "Already running as ID $pid, now quitting"
+ rm $pidfile
+ kill -15 $pid
+ exit 0
+fi
+
+if [[ "$1" == "" ]]; then
+ echo "Usage: ${0##*/} <window-id>"
+ exit 2
+fi
+
+windowId=$1
+tmpFile=/tmp/screencast-$(date +%s).mkv
+paletteFile=/tmp/palette-$(date +%s).png
+gifFile=/tmp/screencast-$(date +%s).gif
+
+size=$(xdotool getwindowgeometry $windowId | grep Geometry | awk '{print $2}')
+posX=$(xwininfo -id $windowId | grep 'Absolute upper-left X' | awk '{print $4}')
+posY=$(xwininfo -id $windowId | grep 'Absolute upper-left Y' | awk '{print $4}')
+pos="$posX,$posY"
+
+ffmpeg -hide_banner -loglevel info -f x11grab -show_region 1 -video_size $size -i :0+$pos $tmpFile &
+ffPID=$!
+echo $ffPID >$pidfile
+wait $ffPID && echo
+
+ffmpeg -y -i $tmpFile -vf fps=10,palettegen $paletteFile
+ffmpeg -i $tmpFile -i $paletteFile -filter_complex "paletteuse" $gifFile
+
+rm $paletteFile $tmpFile
diff --git a/ar/.local/bin/sd b/ar/.local/bin/sd
new file mode 100755
index 0000000..67b0d5a
--- /dev/null
+++ b/ar/.local/bin/sd
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# Open a terminal window in the same directory as the currently active window.
+
+windowPID=$(xprop -id "$(xprop -root | sed -n "/_NET_ACTIVE_WINDOW/ s/^.*# // p")" | sed -n "/PID/ s/^.*= // p")
+PIDlist=$(pstree -lpATna "$windowPID" | sed -En 's/.*,([0-9]+).*/\1/p' | tac)
+for PID in $PIDlist; do
+ cmdline=$(ps -o args= -p "$PID")
+ process_group_leader=$(ps -o comm= -p "$(ps -o pgid= -p "$PID" | tr -d ' ')")
+ cwd=$(readlink /proc/"$PID"/cwd)
+ # zsh and lf won't be ignored even if it shows ~ or /
+ case "$cmdline" in
+ 'lf -server') continue ;;
+ "${SHELL##*/}" | 'lf' | 'lf '*) break ;;
+ esac
+ # git (and its sub-processes) will show the root of a repository instead of the actual cwd, so they're ignored
+ [ "$process_group_leader" = 'git' ] || [ ! -d "$cwd" ] && continue
+ # This is to ignore programs that show ~ or / instead of the actual working directory
+ [ "$cwd" != "$HOME" ] && [ "$cwd" != '/' ] && break
+done
+[ "$PWD" != "$cwd" ] && [ -d "$cwd" ] && { cd "$cwd" || exit 1; }
+setsid -f "$TERMINAL"
diff --git a/ar/.local/bin/sessionizer b/ar/.local/bin/sessionizer
new file mode 100755
index 0000000..5857dd3
--- /dev/null
+++ b/ar/.local/bin/sessionizer
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+sessionizer() {
+ [ -n "$TMUX" ] && gitpath=$(tmux display-message -p '#{pane_current_path}') || gitpath="$PWD"
+ path="$(
+ sesh list -c -H | fzf-tmux -p 80%,70% \
+ --no-sort --cycle --ignore-case --ansi --border=sharp --multi --border-label "╢ TheSiahxyz ╟" --prompt "💡 " \
+ --header "^a all ^e sesh ^f zoxide ^g git ^t tmux ^u staged files ^x tmux kill M-cr open in editor ^/ help" \
+ --reverse \
+ --bind "ctrl-a:change-prompt(💡 )+reload(sesh list -H)" \
+ --bind "ctrl-e:change-prompt(📑 )+reload(sesh list -c -H)" \
+ --bind "ctrl-f:change-prompt(🔎 )+reload(sesh list -z -H)" \
+ --bind "ctrl-g:change-prompt( )+reload(fd -H -d 1 -t d -E .Trash -E .git -E .cache . $HOME/Private/repos $HOME/Public/repos | sed 's|$HOME|~|g')" \
+ --bind "ctrl-t:change-prompt(🪟 )+reload(sesh list -t)" \
+ --bind "ctrl-v:execute($EDITOR ${0})+abort" \
+ --bind 'ctrl-x:execute(tmux kill-session -t {})+change-prompt(💡 )+reload(sesh list -t)' \
+ --bind "alt-enter:execute($EDITOR {})+abort" \
+ --bind 'ctrl-/:change-prompt(❓ )+reload(echo "^a all
+^e sesh config
+^f zoxide
+^g git
+^t tmux
+^x tmux kill
+M-cr open in editor
+^/ help")' \
+ --preview-window 'right:60%' \
+ --preview 'sesh preview {}'
+ )" 2>/dev/null
+
+ case "$path" in
+ ^*) sessionizer ;;
+ *) sesh connect "$path" >/dev/null 2>&1 && exit ;;
+ esac
+}
+
+sessionizer
diff --git a/ar/.local/bin/setbg b/ar/.local/bin/setbg
new file mode 100755
index 0000000..66f6819
--- /dev/null
+++ b/ar/.local/bin/setbg
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+# This script does the following:
+# Run by itself, set the wallpaper (at X start).
+# If given a file, set that as the new wallpaper.
+# If given a directory, choose random file in it.
+# If wal is installed, also generates a colorscheme.
+
+# Location of link to wallpaper link.
+wallloc="${XDG_DATA_HOME:-${HOME}/.local/share}/wallpapers"
+bgloc="$wallloc/bg"
+vidloc="$wallloc/video/1/filename00001.jpg"
+
+# Configuration files of applications that have their themes changed by pywal.
+dunstconf="${XDG_CONFIG_HOME:-${HOME}/.config}/dunst/dunstrc"
+zathuraconf="${XDG_CONFIG_HOME:-${HOME}/.config}/zathura/zathurarc"
+
+# Give -s as parameter to make notifications silent.
+while getopts "s" o; do
+ case "${o}" in
+ s) silent='1' ;;
+ *) ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+[ -d "$wallloc" ] || mkdir -p "$wallloc"
+
+[ "$(ls "$wallloc/video" 2>/dev/null)" ] && {
+ [ -n "$(pgrep -f "wallset")" ] && pgrep -f "wallset" | xargs kill
+ wallset -r 2>/dev/null
+ wallset -q 2>/dev/null
+ rm -rf "$wallloc/video"
+}
+
+[ -f ~/.fehbg ] && rm -f ~/.fehbg
+
+trueloc="$(readlink -f "$1")" &&
+ case "$(file --mime-type -b "$trueloc")" in
+ image/*) ln -sf "$trueloc" "$bgloc" && [ -z "$silent" ] && notify-send -i "$bgloc" "Changing wallpaper..." ;;
+ inode/directory) ln -sf "$(find -L "$trueloc" -iregex '.*.\(jpg\|jpeg\|png\|gif\)' -type f | shuf -n 1)" "$bgloc" && [ -z "$silent" ] && notify-send -i "$bgloc" "Random Wallpaper chosen." ;;
+ video/*)
+ mkdir -p "$wallloc/video" 2>/dev/null
+ if [ ! "${trueloc##*.}" = "mp4" ] || [ "$(($(mediainfo --Output="General;%Duration%" "$trueloc")))" -gt 10000 ]; then
+ notify-send "⌛ Converting video for wallpaper..." && ffmpeg -i "$trueloc" -t 3 "$wallloc/video/bg.mp4" 2>/dev/null
+ else
+ cp "$trueloc" "$wallloc/video/bg.mp4" 2>/dev/null
+ fi
+ trueloc="$wallloc/video/bg.mp4"
+ wallset --video "$trueloc" >/dev/null 2>&1 && [ -z "$silent" ] && notify-send -i "$vidloc" "Changing wallpaper..."
+ exit
+ ;;
+ *)
+ [ -z "$silent" ] && notify-send "🖼️ Error" "Not a valid image or directory."
+ exit 1
+ ;;
+ esac
+
+# If pywal is installed, use it.
+if command -v wal >/dev/null 2>&1; then
+ wal -n -i "$(readlink -f "$bgloc")" -o "${XDG_CONFIG_HOME:-${HOME}/.config}/wal/postrun" >/dev/null 2>&1
+# If pywal is removed, return config files to normal.
+else
+ [ -f "$dunstconf.bak" ] && unlink "$dunstconf" && mv "$dunstconf.bak" "$dunstconf"
+ [ -f "$zathuraconf.bak" ] && unlink "$zathuraconf" && mv "$zathuraconf.bak" "$zathuraconf"
+fi
+
+xwallpaper --zoom "$bgloc"
+# If running, dwm hit the key to refresh the color scheme.
+pidof dwm >/dev/null && xdotool key super+F5
diff --git a/ar/.local/bin/setlock b/ar/.local/bin/setlock
new file mode 100755
index 0000000..611ae5e
--- /dev/null
+++ b/ar/.local/bin/setlock
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+lockloc="${XDG_DATA_HOME:-${HOME}/.local/share}/wallpapers/lock"
+
+# Give -s as parameter to make notifications silent.
+while getopts "s" o; do
+ case "${o}" in
+ s) silent='1' ;;
+ *) ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+trueloc="$(readlink -f "$1")" &&
+ case "$(file --mime-type -b "$trueloc")" in
+ image/*) ln -sf "$trueloc" "$lockloc" && [ -z "$silent" ] && notify-send -i "$lockloc" "Changing lock screen..." ;;
+ inode/directory) ln -sf "$(find -L "$trueloc" -iregex '.*.\(jpg\|jpeg\|png\|gif\)' -type f | shuf -n 1)" "$lockloc" && [ -z "$silent" ] && notify-send -i "$lockloc" "Random Lock Screen chosen." ;;
+ *)
+ [ -z "$silent" ] && notify-send "🖼️ Error" "Not a valid image or directory."
+ exit 1
+ ;;
+ esac
diff --git a/ar/.local/bin/setmonitor b/ar/.local/bin/setmonitor
new file mode 100755
index 0000000..66e2284
--- /dev/null
+++ b/ar/.local/bin/setmonitor
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# Parse connected displays
+default="--mode 1920x1080 --rotate normal --scale 1.0x1.0 --dpi 96"
+
+for connected in $(xrandr -q | grep "\sconnected" | awk '{print $1}'); do
+ case $connected in
+ eDP*) edp="$connected" ;;
+ HDMI*) hdmi="$connected" ;;
+ DP*) dp="$connected" ;;
+ *) display="$connected" ;;
+ esac
+done
+
+# If the lid is closed, turn off the laptop's screen
+if grep -q "disabled" /sys/class/drm/card0-eDP-1/enabled || grep -q "closed" /proc/acpi/button/lid/LID/state; then
+ if [ -n "$hdmi" ] && [ -z "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --off --output "$hdmi" --primary $default
+ elif [ -z "$hdmi" ] && [ -n "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --off --output "$dp" --primary $default
+ else
+ xrandr --output "$edp" --off --output "$display" --primary $default
+ fi
+else
+ # Apply display settings when lid is open
+ if [ -n "$hdmi" ] && [ -z "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --pos 1920x0 $default --output "$hdmi" --primary --pos 0x0 $default
+ elif [ -z "$hdmi" ] && [ -n "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --pos 1920x0 $default --output "$dp" --primary --pos 0x0 $default
+ elif [ -z "$hdmi" ] && [ -z "$dp" ] && [ -n "$edp" ]; then
+ xrandr --output "$edp" --primary $default
+ else
+ xrandr --output "$display" --primary --auto
+ fi
+fi
diff --git a/ar/.local/bin/shortcuts b/ar/.local/bin/shortcuts
new file mode 100755
index 0000000..e7c96c1
--- /dev/null
+++ b/ar/.local/bin/shortcuts
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+bmdirs="${XDG_CONFIG_HOME:-${HOME}/.config}/shell/bm-dirs"
+bmfiles="${XDG_CONFIG_HOME:-${HOME}/.config}/shell/bm-files"
+
+# Output locations. Unactivated progs should go to /dev/null.
+shell_shortcuts="${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutrc"
+shell_env_shortcuts="${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutenvrc"
+zsh_named_dirs="${XDG_CONFIG_HOME:-${HOME}/.config}/shell/zshnameddirrc"
+lf_shortcuts="${XDG_CONFIG_HOME:-${HOME}/.config}/lf/shortcutrc"
+vim_shortcuts="${XDG_CONFIG_HOME:-${HOME}/.config}/vim/shortcuts.vim"
+nvim_shortcuts="${XDG_CONFIG_HOME:-${HOME}/.config}/nvim/shortcuts.lua"
+ranger_shortcuts="/dev/null"
+qute_shortcuts="/dev/null"
+fish_shortcuts="/dev/null"
+vifm_shortcuts="/dev/null"
+
+# Remove, prepare files
+rm -f "$lf_shortcuts" "$ranger_shortcuts" "$qute_shortcuts" "$zsh_named_dirs" "$vim_shortcuts" "$nvim_shortcuts" 2>/dev/null
+printf "# vim: filetype=sh\\n" >"$fish_shortcuts"
+printf "# vim: filetype=sh\\nalias " >"$shell_shortcuts"
+printf "# vim: filetype=sh\\n" >"$shell_env_shortcuts"
+printf "\" vim: filetype=vim\\n" >"$vifm_shortcuts"
+
+# Format the `directories` file in the correct syntax and sent it to all three configs.
+eval "echo \"$(cat "$bmdirs")\"" |
+ awk "!/^\s*#/ && !/^\s*\$/ {gsub(\"\\\s*#.*$\",\"\");
+ printf(\"%s=\42cd %s && ls -A\42 \\\\\n\",\$1,\$2) >> \"$shell_shortcuts\" ;
+ printf(\"[ -n \42%s\42 ] && export %s=\42%s\42 \n\",\$1,\$1,\$2) >> \"$shell_env_shortcuts\" ;
+ printf(\"hash -d %s=%s \n\",\$1,\$2) >> \"$zsh_named_dirs\" ;
+ printf(\"abbr %s \42cd %s; and ls -A\42\n\",\$1,\$2) >> \"$fish_shortcuts\" ;
+ printf(\"map g%s :cd %s<CR>\nmap t%s <tab>:cd %s<CR><tab>\nmap M%s <tab>:cd %s<CR><tab>:mo<CR>\nmap Y%s <tab>:cd %s<CR><tab>:co<CR> \n\",\$1,\$2, \$1, \$2, \$1, \$2, \$1, \$2) >> \"$vifm_shortcuts\" ;
+ printf(\"config.bind(';%s', \42set downloads.location.directory %s ;; hint links download\42) \n\",\$1,\$2) >> \"$qute_shortcuts\" ;
+ printf(\"map g%s cd %s\nmap t%s tab_new %s\nmap m%s shell mv -v %%s %s\nmap Y%s shell cp -rv %%s %s \n\",\$1,\$2,\$1,\$2, \$1, \$2, \$1, \$2) >> \"$ranger_shortcuts\" ;
+ printf(\"map %s cd \42%s\42 \n\",\$1,\$2) >> \"$lf_shortcuts\" ;
+ printf(\"cmap ;%s %s\n\",\$1,\$2) >> \"$vim_shortcuts\" ;
+ printf(\"nmap <localleader><localleader>%s :Explore %s<cr>\n\",\$1,\$2) >> \"$vim_shortcuts\" ;
+ printf(\"vim.keymap.set('c', ';%s', '%s<cr>', { noremap = true, silent = true, desc = '%s' })\n\", \$1, \$2, gensub(\"^/home/si/\",\"~/\",\"g\",\$2)) >> \"$nvim_shortcuts\" ;
+ printf(\"vim.keymap.set('n', '<localleader><leader>%s', '<cmd>Explore %s<cr>', { noremap = true, silent = true, desc = '%s' })\n\", \$1, \$2, gensub(\"^/home/si/\",\"~/\",\"g\",\$2)) >> \"$nvim_shortcuts\" ;
+ printf(\"vim.keymap.set('n', '<localleader><localleader>%s', function() require('mini.files').open('%s') end, { noremap = true, silent = true, desc = '%s' })\n\", \$1, \$2, gensub(\"^/home/si/\",\"~/\",\"g\",\$2)) >> \"$nvim_shortcuts\"}"
+
+# Format the `files` file in the correct syntax and sent it to both configs.
+eval "echo \"$(cat "$bmfiles")\"" |
+ awk "!/^\s*#/ && !/^\s*\$/ {gsub(\"\\\s*#.*$\",\"\");
+ printf(\"%s=\42\$EDITOR %s\42 \\\\\n\",\$1,\$2) >> \"$shell_shortcuts\" ;
+ printf(\"[ -n \42%s\42 ] && export %s=\42%s\42 \n\",\$1,\$1,\$2) >> \"$shell_env_shortcuts\" ;
+ printf(\"v%s=\42\$EDITOR2 %s\42 \\\\\n\",\$1,\$2) >> \"$shell_shortcuts\" ;
+ printf(\"hash -d %s=%s \n\",\$1,\$2) >> \"$zsh_named_dirs\" ;
+ printf(\"abbr %s \42\$EDITOR %s\42 \n\",\$1,\$2) >> \"$fish_shortcuts\" ;
+ printf(\"abbr v%s \42\$EDITOR2 %s\42 \n\",\$1,\$2) >> \"$fish_shortcuts\" ;
+ printf(\"map %s :e %s<CR> \n\",\$1,\$2) >> \"$vifm_shortcuts\" ;
+ printf(\"map %s shell \$EDITOR %s \n\",\$1,\$2) >> \"$ranger_shortcuts\" ;
+ printf(\"map v%s shell \$EDITOR2 %s \n\",\$1,\$2) >> \"$ranger_shortcuts\" ;
+ printf(\"map %s \$\$EDITOR \42%s\42 \n\",\$1,\$2) >> \"$lf_shortcuts\" ;
+ printf(\"map v%s \$\$EDITOR2 \42%s\42 \n\",\$1,\$2) >> \"$lf_shortcuts\" ;
+ printf(\"cmap ;%s %s\n\",\$1,\$2) >> \"$vim_shortcuts\" ;
+ printf(\"nmap <localleader><localleader>%s :e %s<cr>\n\",\$1,\$2) >> \"$vim_shortcuts\" ;
+ printf(\"vim.keymap.set('c', ';%s', '%s<cr>', { noremap = true, silent = true, desc = '%s' })\n\", \$1, \$2, gensub(\"^/home/si/\",\"~/\",\"g\",\$2)) >> \"$nvim_shortcuts\" ;
+ printf(\"vim.keymap.set('n', '<localleader><localleader>%s', '<cmd>e %s<cr>', { noremap = true, silent = true, desc = '%s' })\n\", \$1, \$2, gensub(\"^/home/si/\",\"~/\",\"g\",\$2)) >> \"$nvim_shortcuts\"}"
+
+# root
+root_shell_shortcuts="/root/.config/shell/rootshortcutrc"
+root_zsh_named_dirs="/root/.config/shell/rootzshnameddirrc"
+root_lf_shortcuts="/root/.config/lf/rootshortcutrc"
+root_vim_shortcuts="/root/.config/vim/rootshortcuts.vim"
+
+sudo rm -f "$root_zsh_named_dirs" "$root_lf_shortcuts" "$root_vim_shortcuts" 2>/dev/null
+printf "# vim: filetype=sh\\nalias " | sudo tee "$root_shell_shortcuts" 2>/dev/null
+sudo mkdir -p /root/.config/shell/ /root/.config/lf/ /root/.config/vim/
+sudo touch "$root_shell_shortcuts" "$root_zsh_named_dirs" "$root_lf_shortcuts" "$root_vim_shortcuts"
+
+eval "echo \"$(cat "$bmdirs")\"" |
+ sudo awk "!/^\s*#/ && !/^\s*\$/ && /cache|config($|\/bash|\/lf|\/shell|\/vim)|local\/(bin|share|state)$/ {gsub(\"\\\s*#.*$\",\"\"); gsub(\"home/si\", \"root\");
+ printf(\",%s=\42cd %s && ls -A\42 \\\\\n\",\$1,\$2) >> \"$root_shell_shortcuts\" ;
+ printf(\"hash -d ,%s=%s \n\",\$1,\$2) >> \"$root_zsh_named_dirs\" ;
+ printf(\"map ,%s cd \42%s\42 \n\",\$1,\$2) >> \"$root_lf_shortcuts\" ;
+ printf(\"cmap ;%s %s\n\",\$1,\$2) >> \"$root_vim_shortcuts\" ;
+ printf(\"nmap <localleader><localleader>%s :Explore %s<cr>\n\",\$1,\$2) >> \"$root_vim_shortcuts\"}"
+
+eval "echo \"$(cat "$bmfiles")\"" |
+ sudo awk "!/^\s*#/ && !/^\s*\$/ && /config\/(bash|lf|vim)\/.*rc |inputrc|\$EDITOR / {gsub(\"\\\s*#.*$\",\"\"); gsub(\"home/si\", \"root\");
+ printf(\",%s=\42\$EDITOR %s\42 \\\\\n\",\$1,\$2) >> \"$root_shell_shortcuts\" ;
+ printf(\"hash -d ,%s=%s \n\",\$1,\$2) >> \"$root_zsh_named_dirs\" ;
+ printf(\"map ,%s \$\$EDITOR \42%s\42 \n\",\$1,\$2) >> \"$root_lf_shortcuts\" ;
+ printf(\"cmap ;%s %s\n\",\$1,\$2) >> \"$root_vim_shortcuts\" ;
+ printf(\"nmap <localleader><localleader>%s :e %s<cr>\n\",\$1,\$2) >> \"$root_vim_shortcuts\"}"
diff --git a/ar/.local/bin/slider b/ar/.local/bin/slider
new file mode 100755
index 0000000..35bb6a7
--- /dev/null
+++ b/ar/.local/bin/slider
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+# Give a file with images and timecodes and creates a video slideshow of them.
+#
+# Timecodes must be in format 00:00:00.
+#
+# Imagemagick and ffmpeg required.
+
+# Application cache if not stated elsewhere.
+cache="${XDG_CACHE_HOME:-${HOME}/.cache}/slider"
+
+while getopts "hvrpi:c:a:o:d:f:t:e:x:s:" o; do case "${o}" in
+ c) bgc="$OPTARG" ;;
+ t) fgc="$OPTARG" ;;
+ f) font="$OPTARG" ;;
+ i) file="$OPTARG" ;;
+ a) audio="$OPTARG" ;;
+ o) outfile="$OPTARG" ;;
+ d) prepdir="$OPTARG" ;;
+ r) redo="$OPTARG" ;;
+ s) ppt="$OPTARG" ;;
+ e) endtime="$OPTARG" ;;
+ x)
+ res="$OPTARG"
+ echo "$res" | grep -qv "^[0-9]\+x[0-9]\+$" &&
+ echo "Resolution must be dimensions separated by a 'x': 1280x720, etc." &&
+ exit 1
+ ;;
+ p)
+ echo "Purge old build files in $cache? [y/N]"
+ read -r confirm
+ echo "$confirm" | grep -iq "^y$" && rm -rf "$cache" && echo "Done."
+ exit
+ ;;
+ v) verbose=True ;;
+ *) echo "$(basename "$0") usage:
+ -i input timecode list (required)
+ -a audio file
+ -c color of background (use html names, black is default)
+ -t text color for text slides (white is default)
+ -s text font size for text slides (150 is default)
+ -f text font for text slides (sans serif is default)
+ -o output video file
+ -e if no audio given, the time in seconds that the last slide will be shown (5 is default)
+ -x resolution (1920x1080 is default)
+ -d tmp directory
+ -r rerun imagemagick commands even if done previously (in case files or background has changed)
+ -p purge old build files instead of running
+ -v be verbose" && exit 1 ;;
+
+ esac done
+
+# Check that the input file looks like it should.
+{ head -n 1 "$file" 2>/dev/null | grep -q "^00:00:00 "; } || {
+ echo "Give an input file with -i." &&
+ echo "The file should look as this example:
+
+00:00:00 first_image.jpg
+00:00:03 otherdirectory/next_image.jpg
+00:00:09 this_image_starts_at_9_seconds.jpg
+etc...
+
+Timecodes and filenames must be separated by Tabs." &&
+ exit 1
+}
+
+if [ -n "${audio+x}" ]; then
+ # Check that the audio file looks like an actual audio file.
+ case "$(file --dereference --brief --mime-type -- "$audio")" in
+ audio/*) ;;
+ *)
+ echo "That doesn't look like an audio file."
+ exit 1
+ ;;
+ esac
+ totseconds="$(date '+%s' -d $(ffmpeg -i "$audio" 2>&1 | awk '/Duration/ {print $2}' | sed s/,//))"
+fi
+
+prepdir="${prepdir:-$cache/$file}"
+outfile="${outfile:-$file.mp4}"
+prepfile="$prepdir/$file.prep"
+
+[ -n "${verbose+x}" ] && echo "Preparing images... May take a while depending on the number of files."
+mkdir -p "$prepdir"
+
+{
+ while read -r x; do
+ # Get the time from the first column.
+ time="${x%% *}"
+ seconds="$(date '+%s' -d "$time")"
+ # Duration is not used on the first looped item.
+ duration="$((seconds - prevseconds))"
+
+ # Get the filename/text content from the rest.
+ content="${x#* }"
+ base="$(basename "$content")"
+ base="${base%.*}.jpg"
+
+ if [ -f "$content" ]; then
+ # If images have already been made in a previous run, do not recreate
+ # them unless -r was given.
+ { [ ! -f "$prepdir/$base" ] || [ -n "${redo+x}" ]; } &&
+ magick -size "${res:-1920x1080}" canvas:"${bgc:-black}" -gravity center "$content" -resize 1920x1080 -composite "$prepdir/$base"
+ else
+ { [ ! -f "$prepdir/$base" ] || [ -n "${redo+x}" ]; } &&
+ magick -size "${res:-1920x1080}" -background "${bgc:-black}" -fill "${fgc:-white}" -font "${font:-Sans}" -pointsize "${ppt:-150}" -gravity center label:"$content" "$prepdir/$base"
+ fi
+
+ # If the first line, do not write yet.
+ [ "$time" = "00:00:00" ] || echo "file '$prevbase'
+duration $duration"
+
+ # Keep the information required for the next file.
+ prevbase="$base"
+ prevtime="$time"
+ prevseconds="$(date '+%s' -d "$prevtime")"
+ done <"$file"
+ # Do last file which must be given twice as follows
+ endtime="$((totseconds - seconds))"
+ echo "file '$base'
+duration ${endtime:-5}
+file '$base'"
+} >"$prepfile"
+if [ -n "${audio+x}" ]; then
+ ffmpeg -hide_banner -y -f concat -safe 0 -i "$prepfile" -i "$audio" -c:a aac -vsync vfr -c:v libx264 -pix_fmt yuv420p "$outfile"
+else
+ ffmpeg -hide_banner -y -f concat -safe 0 -i "$prepfile" -vsync vfr -c:v libx264 -pix_fmt yuv420p "$outfile"
+fi
+
+# Might also try:
+# -vf "fps=${fps:-24},format=yuv420p" "$outfile"
+# but has given some problems.
diff --git a/ar/.local/bin/sshadd b/ar/.local/bin/sshadd
new file mode 100755
index 0000000..76797ba
--- /dev/null
+++ b/ar/.local/bin/sshadd
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+SSH_KEY_DIR="${PASSWORD_STORE_DIR:-${HOME}/.local/share/.password-store}"
+
+temp_private_keys_list=$(mktemp)
+
+# Ensure that filenames with spaces or other special characters are handled correctly.
+find "$SSH_KEY_DIR" -name "*.pub" | while IFS= read -r pub_file_path; do
+ private_key_path="${pub_file_path%.pub}"
+ if [ -f "$private_key_path" ]; then
+ echo "$(basename "$private_key_path")" >>"$temp_private_keys_list"
+ echo "$private_key_path" >>"$temp_private_keys_list"
+ fi
+done
+
+# Use of dmenu is system-specific and not covered by POSIX standards
+selected_key_name=$(awk 'NR % 2 == 1' "$temp_private_keys_list" | dmenu -i -p "Select SSH key:")
+
+if [ -n "$selected_key_name" ]; then
+ selected_key_path=$(awk -v name="$selected_key_name" '$0 == name { getline; print }' "$temp_private_keys_list")
+
+ if [ -n "$selected_key_path" ]; then
+ export SSH_ASKPASS="$HOME/.local/bin/demupass"
+ setsid ssh-add "$selected_key_path" </dev/null
+ ln -sf "$selected_key_path" "$HOME/.ssh/$(basename "$selected_key_path")"
+ ln -sf "${selected_key_path}.pub" "$HOME/.ssh/$(basename "$selected_key_path").pub"
+ notify-send "🔑 SSH key added:" "$selected_key_name"
+ fi
+fi
+
+rm "$temp_private_keys_list"
diff --git a/ar/.local/bin/statusbar/sb-battery b/ar/.local/bin/statusbar/sb-battery
new file mode 100755
index 0000000..faf3d04
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-battery
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+# Prints all batteries, their percentage remaining and an emoji corresponding
+# to charge status (🔌 for plugged up, 🔋 for discharging on battery, etc.).
+
+# Function to get the battery status icon
+get_status_icon() {
+ case "$1" in
+ Full) echo "⚡" ;;
+ Discharging) echo "🔋" ;;
+ Charging) echo "🔌" ;;
+ "Not charging") echo "🛑" ;;
+ Unknown) echo "♻️" ;;
+ *) echo "" ;;
+ esac
+}
+
+# Function to print battery status
+battery_status() {
+ device=$1
+ capacity=$(cat "$device/capacity" 2>/dev/null)
+ status=$(cat "$device/status" 2>/dev/null)
+ icon=$(get_status_icon "$status")
+ [ -z "$icon" ] && return
+ case $(basename "$device") in
+ BAT?*)
+ [ "$icon" = "🔋" ] && [ "$capacity" -le 25 ] && warn="❗"
+ printf "%s%s%d%%" "$icon" "$warn" "$capacity"
+ ;;
+ hid*)
+ model="$(cat "$device/model_name")"
+ notify-send "$icon $model:" "$capacity%"
+ ;;
+ esac
+ unset warn
+}
+
+devices() {
+ for battery in /sys/class/power_supply/$1; do
+ battery_status "$battery"
+ done && printf "\\n"
+}
+
+bluetooth() {
+ bluedevices=$(upower -e | grep -iv 'line' | grep -iv 'display' | grep -v 'BAT[0-9]' | grep -v 'hid')
+ for bluedevice in $bluedevices; do
+ bluedevice_name=$(upower -i "$bluedevice" | grep "model" | awk -F ': ' '{print $2}' | sed 's/ //g')
+ bluedevice_battery=$(upower -i "$bluedevice" | grep "percentage" | awk -F ': ' '{print $2}' | sed 's/ //g')
+ if [ -n "$bluedevice_battery" ]; then
+ notify-send "🔋 $bluedevice_name:" "$bluedevice_battery"
+ fi
+ done
+}
+
+# Handle mouse click actions
+case "$BLOCK_BUTTON" in
+2) bluetooth && devices "hid*" ;; # Middle click for Bluetooth battery levels
+3) notify-send "🔋 Battery module" "\- 🔋: discharging
+- 🛑: not charging
+- ♻️: stagnant charge
+- 🔌: charging
+- ⚡: fully charged
+- ❗: battery very low!
+- Middle click: bluetooth battery levels via upower" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+devices "BAT?*"
diff --git a/ar/.local/bin/statusbar/sb-bghitness b/ar/.local/bin/statusbar/sb-bghitness
new file mode 100755
index 0000000..0aabfb6
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-bghitness
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# current brightness
+curr_brightness=$(cat /sys/class/backlight/*/brightness)
+
+# max_brightness
+max_brightness=$(cat /sys/class/backlight/*/max_brightness)
+
+# brightness percentage
+brightness_per=$((100 * curr_brightness / max_brightness))
+
+case $BLOCK_BUTTON in
+3) notify-send "💡 Brightness module" "\- Shows current brightness level ☀️." ;;
+4) pkexec brillo -A 5 -q ;;
+5) pkexec brillo -U 5 -q ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+echo "💡${brightness_per}%"
diff --git a/ar/.local/bin/statusbar/sb-brightness b/ar/.local/bin/statusbar/sb-brightness
new file mode 100755
index 0000000..909e676
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-brightness
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+case $BLOCK_BUTTON in
+3) notify-send "🔆 Backlight module
+- Scroll up & down changes backlight" ;;
+4) monbright -inc 5 ;;
+5) monbright -dec 5 ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+monitorbright
diff --git a/ar/.local/bin/statusbar/sb-clock b/ar/.local/bin/statusbar/sb-clock
new file mode 100755
index 0000000..e0e8c53
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-clock
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Get current hour and minute
+hour=$(date '+%I')
+minute=$(date '+%M')
+calendar="🗓️"
+
+# Determine the icon based on hour and minute
+if crontab -l 2>/dev/null | grep -q '^[^#[:space:]]'; then
+ if [ "$minute" -ge 30 ]; then
+ case "$hour" in
+ "00" | "12") icon="🕧" ;; # 12:30
+ "01" | "13") icon="🕜" ;; # 1:30
+ "02" | "14") icon="🕝" ;; # 2:30
+ "03" | "15") icon="🕞" ;; # 3:30
+ "04" | "16") icon="🕟" ;; # 4:30
+ "05" | "17") icon="🕠" ;; # 5:30
+ "06" | "18") icon="🕡" ;; # 6:30
+ "07" | "19") icon="🕢" ;; # 7:30
+ "08" | "20") icon="🕣" ;; # 8:30
+ "09" | "21") icon="🕤" ;; # 9:30
+ "10" | "22") icon="🕥" ;; # 10:30
+ "11" | "23") icon="🕦" ;; # 11:30
+ esac
+ else
+ case "$hour" in
+ "00" | "12") icon="🕛" ;; # 12:00
+ "01" | "13") icon="🕐" ;; # 1:00
+ "02" | "14") icon="🕑" ;; # 2:00
+ "03" | "15") icon="🕒" ;; # 3:00
+ "04" | "16") icon="🕓" ;; # 4:00
+ "05" | "17") icon="🕔" ;; # 5:00
+ "06" | "18") icon="🕕" ;; # 6:00
+ "07" | "19") icon="🕖" ;; # 7:00
+ "08" | "20") icon="🕗" ;; # 8:00
+ "09" | "21") icon="🕘" ;; # 9:00
+ "10" | "22") icon="🕙" ;; # 10:00
+ "11" | "23") icon="🕚" ;; # 11:00
+ esac
+ fi
+else
+ icon="⏰"
+fi
+
+# Shows the current moon phase.
+moonfile="${XDG_DATA_HOME:-${HOME}/.local/share}/moonphase"
+
+[ -s "$moonfile" ] && [ "$(stat -c %y "$moonfile" 2>/dev/null | cut -d' ' -f1)" = "$(date '+%Y-%m-%d')" ] ||
+ { curl -sf "wttr.in/?format=%m" >"$moonfile" || exit 1; }
+
+moon="$(cat "$moonfile")"
+
+case $BLOCK_BUTTON in
+1)
+ notify-send "This Month" "$(cal | sed "s/\<$(date +'%B' | tr -d ' ')\>/<b><span color='blue'>&<\/span><\/b>/;s/\<$(date +'%Y' | sed 's/ //g')\>/<b><span color='blue'>&<\/span><\/b>/;s/\<$(date +'%B' | sed 's/ //g')\>/<b><span color='blue'>&<\/span><\/b>/;s/\<$(date +'%e' | sed 's/ //g')\>/<b><span color='red'>&<\/span><\/b>/")" && notify-send "Appointments" "$(calcurse -d3)"
+ ;;
+2) setsid -f "$TERMINAL" -e calcurse ;;
+3)
+ notify-send "📅 Time/date module" "\- Left click to show upcoming appointments for the next three days via \`calcurse -d3\` and show the month via \`cal\`
+- Left click also displays the current time in other cities.
+- Middle click opens calcurse if installed"
+ notify-send "🌜 Moon phase module" "Displays current moon phase
+- 🌑: New
+- 🌒: Waxing Crescent
+- 🌓: First Quarter
+- 🌔: Waxing Gibbous
+- 🌕: Full
+- 🌖: Waning Gibbous
+- 🌗: Last Quarter
+- 🌘: Waning Crescent"
+ ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+7) timezones ;;
+esac
+
+# Output the formatted date and time
+date "+${moon-$calendar}%a,%d $icon%H:%M"
diff --git a/ar/.local/bin/statusbar/sb-cpu b/ar/.local/bin/statusbar/sb-cpu
new file mode 100755
index 0000000..9dfbb54
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-cpu
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+case $BLOCK_BUTTON in
+1) notify-send "🖥 CPU hogs" "$(ps axch -o cmd:15,%cpu --sort=-%cpu | head)\\n(100% per core)" ;;
+2) setsid -f "$TERMINAL" -e htop ;;
+3) notify-send "🖥 CPU module " "\- Shows CPU temperature
+- Left click to show intensive processes
+- Middle click to open htop" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+sensors | awk '/Core 0/ {printf "🍉%d°\n", $3}'
diff --git a/ar/.local/bin/statusbar/sb-cpubars b/ar/.local/bin/statusbar/sb-cpubars
new file mode 100755
index 0000000..ce677ef
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-cpubars
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Module showing CPU load as a changing bars.
+# Just like in polybar.
+# Each bar represents amount of load on one core since
+# last run.
+
+# Cache in tmpfs to improve speed and reduce SSD load
+cache=/tmp/cpubarscache
+
+case $BLOCK_BUTTON in
+2) setsid -f "$TERMINAL" -e htop ;;
+3) notify-send "🪨 CPU load module" "Each bar represents one CPU core" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+# id total idle
+stats=$(awk '/cpu[0-9]+/ {printf "%d %d %d\n", substr($1,4), ($2 + $3 + $4 + $5), $5 }' /proc/stat)
+[ ! -f $cache ] && echo "$stats" >"$cache"
+old=$(cat "$cache")
+printf "🪨"
+echo "$stats" | while read -r row; do
+ id=${row%% *}
+ rest=${row#* }
+ total=${rest%% *}
+ idle=${rest##* }
+
+ case "$(echo "$old" | awk '{if ($1 == id)
+ printf "%d\n", (1 - (idle - $3) / (total - $2))*100 /12.5}' \
+ id="$id" total="$total" idle="$idle")" in
+
+ "0") printf "▁" ;;
+ "1") printf "▂" ;;
+ "2") printf "▃" ;;
+ "3") printf "▄" ;;
+ "4") printf "▅" ;;
+ "5") printf "▆" ;;
+ "6") printf "▇" ;;
+ "7") printf "█" ;;
+ "8") printf "█" ;;
+ esac
+done
+printf "\\n"
+echo "$stats" >"$cache"
diff --git a/ar/.local/bin/statusbar/sb-disk b/ar/.local/bin/statusbar/sb-disk
new file mode 100755
index 0000000..3c92f00
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-disk
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Status bar module for disk space
+# $1 should be drive mountpoint, otherwise assumed /.
+
+location=${1:-/}
+
+[ -d "$location" ] || exit
+
+case "$location" in
+"/home"*) icon="🏠" ;;
+"/mnt"*) icon="💾" ;;
+*) icon="💻" ;;
+esac
+
+usage=$(df -h "$location" | awk ' /[0-9]/ {print $3 "/" $2}')
+
+case $BLOCK_BUTTON in
+1) notify-send "💽 Disk space" "$(df -h --output=target,used,size)" ;;
+2) notify-send "💽 Disk usage" "$icon: $usage" ;;
+3) notify-send "💽 Disk module" "\- Shows used hard drive space
+- Left click to show all disk info
+- Middle click to show disk usage" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+printf "%s%s\n" "$icon" "$(df -hP "$location" | awk ' /[0-9]/ {print $5}')"
diff --git a/ar/.local/bin/statusbar/sb-ecrypt b/ar/.local/bin/statusbar/sb-ecrypt
new file mode 100755
index 0000000..24c4cb3
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-ecrypt
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+is_mounted() { mount | grep -q "$HOME/Private"; }
+
+case $BLOCK_BUTTON in
+1) "${XDG_SCRIPTS_HOME:-${HOME}/.local/bin}/ecrypt" ;;
+3) notify-send "🔒 Encrypted Media Folder " "\- Shows mount status of Media
+- Left click to toggle mount" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+is_mounted "$MOUNT_POINT" && echo "🔑" || echo "🔒"
diff --git a/ar/.local/bin/statusbar/sb-forecast b/ar/.local/bin/statusbar/sb-forecast
new file mode 100755
index 0000000..9966c61
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-forecast
@@ -0,0 +1,402 @@
+#!/bin/sh
+
+# Displays today's snow chance (🏂), precipication chance (☔), humidity (💧), wind speed (🎐), and current (feel like) temperature (🌞).
+# Usually intended for the statusbar.
+
+LOCATION=$(curl -s http://ip-api.com/json | jq -r '[.regionName, .countryCode] | join(",")')
+
+url="${WTTRURL:-wttr.in}"
+weatherreport="${XDG_CACHE_HOME:-${HOME}/.cache}/weatherreport"
+weatherreportjson="${XDG_CACHE_HOME:-${HOME}/.cache}/weatherreport.json"
+
+error() {
+ notify-send -u critical "⛔ Failed to update 'weather$1'"
+ exit 1
+}
+
+# Get a weather report from 'wttr.in' and save it locally.
+getweatherreport() {
+ (timeout --signal=1 10s curl -sf "$url/$LOCATION" >"$weatherreport" &&
+ printf "\nUpdated: %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" >>"$weatherreport") ||
+ error "report"
+}
+
+getweatherreportjson() {
+ timeout --signal=1 10s curl -sf "$url/$LOCATION?format=j1" >"$weatherreportjson" || error "reportjson"
+}
+
+# Forecast should be updated every 3 hours, but set 3600 to check for reliability.
+checkforecast() {
+ [ -s "$1" ] && [ "$(($(date +%s) - $(stat -c %Y "$1")))" -le 3600 ]
+}
+
+get_current_hour() { date +%H | sed 's/^0*//'; }
+
+get_nearest_hourly() {
+ current_hour=$(get_current_hour)
+ hour_index=$((current_hour / 3))
+ jq ".weather[0].hourly[$hour_index]" "$weatherreportjson"
+}
+
+getprecip() { get_nearest_hourly | jq -r '.chanceofrain'; }
+
+gethighprecip() { jq -r '.weather[0].hourly[].chanceofrain' "$weatherreportjson" | sort -rn | head -1; }
+
+getsnow() { get_nearest_hourly | jq -r '.chanceofsnow'; }
+
+getwind() { get_nearest_hourly | jq -r '.windspeedKmph'; }
+
+gettemp() { get_nearest_hourly | jq -r '.tempC'; }
+
+getfeelslike() { get_nearest_hourly | jq -r '.FeelsLikeC'; }
+
+getlowtemp() { jq -r '.weather[0].hourly[].tempC' "$weatherreportjson" | sort -n | head -1; }
+
+gethightemp() { jq -r '.weather[0].hourly[].tempC' "$weatherreportjson" | sort -rn | head -1; }
+
+gethumidity() {
+ humidity=$(get_nearest_hourly | jq -r '.humidity')
+ case "$humidity" in
+ [0-9] | [1-2][0-9]) echo "🏜️: $humidity%" ;;
+ [3-4][0-9]) echo "🌲: $humidity%" ;;
+ [5-6][0-9]) echo "💧: $humidity%" ;;
+ [7-8][0-9]) echo "💦: $humidity%" ;;
+ 9[0-9] | 100) echo "🌊: $humidity%" ;;
+ esac
+}
+
+getdesc() { get_nearest_hourly | jq -r '.weatherDesc[0].value' | sed 's/ $//'; }
+
+showweather() {
+ case "$(gettemp)" in
+ -[0-9]* | 0) icon="🥶" ;; # Temperature <= 0
+ [1-9] | [1-2][0-9]) icon="🌞" ;; # Temperature 1–29
+ 3* | [4-9]*) icon="🥵" ;; # Temperature >= 30
+ esac
+ [ -n "$(gettemp)" ] && echo "$icon$(gettemp)°"
+}
+
+todayweather() {
+ printf "🌈 Today's weather: %s\n🏂: %s%%\n☔: %s(%s)m⧸s\n%s\n🎐: %sm/s\n🌞: %s°(%s°)\n🥶: %s°\n🥵: %s°\n" \
+ "$(getdesc)" "$(getsnow)" "$(getprecip)" "$(gethighprecip)" "$(gethumidity)" "$(getwind)" "$(gettemp)" "$(getfeelslike)" "$(getlowtemp)" "$(gethightemp)"
+}
+
+# Show a Doppler RADAR of a user's preferred location.
+
+secs=600 # Download a new doppler radar if one hasn't been downloaded in $secs seconds.
+radarloc="${XDG_CACHE_HOME:-${HOME}/.cache}/radar"
+doppler="${XDG_CACHE_HOME:-${HOME}/.cache}/doppler.gif"
+
+pickloc() {
+ chosen="$(echo "US: CONUS: Continental United States
+US: Northeast
+US: Southeast
+US: PacNorthWest
+US: PacSouthWest
+US: UpperMissVly
+US: SouthMissVly
+US: SouthPlains
+US: NorthRockies
+US: SouthRockies
+US: Alaska
+US: Carib
+US: Hawaii
+US: CentGrLakes
+US: Conus-Large
+US: KABR: Aberdeen, SD
+US: KBIS: Bismarck, ND
+US: KFTG: Denver/Boulder, CO
+US: KDMX: Des Moines, IA
+US: KDTX: Detroit, MI
+US: KDDC: Dodge City, KS
+US: KDLH: Duluth, MN
+US: KCYS: Cheyenne, WY
+US: KLOT: Chicago, IL
+US: KGLD: Goodland, KS
+US: KUEX: Hastings, NE
+US: KGJX: Grand Junction, CO
+US: KGRR: Grand Rapids, MI
+US: KMVX: Fargo/Grand Forks, ND
+US: KGRB: Green Bay, WI
+US: KIND: Indianapolis, IN
+US: KJKL: Jackson, KY
+US: KARX: La Crosse, WI
+US: KILX: Lincoln/Central Illinois, IL
+US: KLVX: Louisville, KY
+US: KMQT: Marquette
+US: KMKX: Milwaukee, WI
+US: KMPX: Minneapolis, MN
+US: KAPX: Gaylord/Alpena, MI
+US: KLNX: North Platte, NE
+US: KIWX: N. Webster/Northern, IN
+US: KOAX: Omaha, NE
+US: KPAH: Paducah, KY
+US: KEAX: Pleasant Hill, MO
+US: KPUX: Pueblo, CO
+US: KDVN: Quad Cities, IA
+US: KUDX: Rapid City, SD
+US: KRIW: Riverton, WY
+US: KSGF: Springfield, MO
+US: KLSX: St. LOUIS, MO
+US: KFSD: Sioux Falls, SD
+US: KTWX: Topeka, KS
+US: KICT: Wichita, KS
+US: KVWX: Paducah, KY
+US: ICAO: Responsible Wfo
+US: KLTX: WILMINGTON, NC
+US: KCCX: State College/Central, PA
+US: KLWX: Sterling, VA
+US: KFCX: Blacksburg/Roanoke, VA
+US: KRAX: Raleigh/Durham, NC
+US: KGYX: Portland, ME
+US: KDIX: Mt Holly/Philadelphia, PA
+US: KPBZ: Pittsburgh, PA
+US: KAKQ: Wakefield, VA
+US: KMHX: Morehead City, NC
+US: KGSP: Greer/Greenville/Sprtbg, SC
+US: KILN: Wilmington/Cincinnati, OH
+US: KCLE: Cleveland, OH
+US: KCAE: Columbia, SC
+US: KBGM: Binghamton, NY
+US: KENX: Albany, NY
+US: KBUF: Buffalo, NY
+US: KCXX: Burlington, VT
+US: KCBW: Caribou, ME
+US: KBOX: Boston /Taunton, MA
+US: KOKX: New York City, NY
+US: KCLX: Charleston, SC
+US: KRLX: Charleston, WV
+US: ICAO: Responsible WFO
+US: KBRO: Brownsville, TX
+US: KABX: Albuquerque, NM
+US: KAMA: Amarillo, TX
+US: KFFC: Peachtree City/Atlanta, GA
+US: KEWX: Austin/Sanantonio, TX
+US: KBMX: Birmingham, AL
+US: KCRP: Corpus Christi, TX
+US: KFWS: Dallas / Ft. Worth, TX
+US: KEPZ: El Paso, TX
+US: KHGX: Houston/ Galveston, TX
+US: KJAX: Jacksonville, FL
+US: KBYX: Key West, FL
+US: KMRX: Morristown/knoxville, TN
+US: KLBB: Lubbock, TX
+US: KLZK: Little Rock, AR
+US: KLCH: Lake Charles, LA
+US: KOHX: Nashville, TN
+US: KMLB: Melbourne, FL
+US: KNQA: Memphis, TN
+US: KAMX: Miami, FL
+US: KMAF: Midland/odessa, TX
+US: KTLX: Norman, OK
+US: KHTX: Huntsville, AL
+US: KMOB: Mobile, AL
+US: KTLH: Tallahassee, FL
+US: KTBW: Tampa Bay Area, FL
+US: KSJT: San Angelo, TX
+US: KINX: Tulsa, OK
+US: KSRX: Tulsa, OK
+US: KLIX: New Orleans/slidell, LA
+US: KDGX: Jackson, MS
+US: KSHV: Shreveport, LA
+US: ICAO: Responsible WFO
+US: KLGX: Seattle / Tacoma, WA
+US: KOTX: Spokane, WA
+US: KEMX: Tucson, AZ
+US: KYUX: Phoenix, AZ
+US: KNKX: San Diego, CA
+US: KMUX: Monterey/san Francisco, CA
+US: KHNX: San Joaquin/hanford, CA
+US: KSOX: San Diego, CA
+US: KATX: Seattle / Tacoma, WA
+US: KIWA: Phoenix, AZ
+US: KRTX: Portland, OR
+US: KSFX: Pocatello, ID
+US: KRGX: Reno, NV
+US: KDAX: Sacramento, CA
+US: KMTX: Salt Lake City, UT
+US: KPDT: Pendleton, OR
+US: KMSX: Missoula, MT
+US: KESX: Las Vegas, NV
+US: KVTX: Los Angeles, CA
+US: KMAX: Medford, OR
+US: KFSX: Flagstaff, AZ
+US: KGGW: Glasgow, MT
+US: KLRX: Elko, NV
+US: KBHX: Eureka, CA
+US: KTFX: Great Falls, MT
+US: KCBX: Boise, ID
+US: KBLX: Billings, MT
+US: KICX: Salt Lake City, UT
+US: ICAO: Responsible Wfo W/ MSCF
+US: PABC: Anchorage, AK
+US: PAPD: Fairbanks, AK
+US: PHKM: Honolulu, HI
+US: PAHG: Anchorage, AK
+US: PAKC: Anchorage, AK
+US: PAIH: Anchorage, AK
+US: PHMO: Honolulu, HI
+US: PAEC: Fairbanks, AK
+US: TJUA: San Juan, PR
+US: PACG: Juneau, AK
+US: PHKI: Honolulu, HI
+US: PHWA: Honolulu, HI
+US: ICAO: Responsible Wfo W/ MSCF
+US: KFDR: Norman, OK
+US: PGUA: Guam
+US: KBBX: Sacramento, CA
+US: KFDX: Albuquerque, NM
+US: KGWX: Jackson, MS
+US: KDOX: Wakefield, VA
+US: KDYX: San Angelo, TX
+US: KEYX: Las Vegas, NV
+US: KEVX: Mobile, AL
+US: KHPX: Paducah, KY
+US: KTYX: Burlington, VT
+US: KGRK: Dallas / Ft. Worth, TX
+US: KPOE: Lake Charles, LA
+US: KEOX: Tallahassee, FL
+US: KHDX: El Paso, TX
+US: KDFX: San Antonio, TX
+US: KMXX: Birmingham, AL
+US: KMBX: Bismarck, ND
+US: KVAX: Jacksonville, FL
+US: KJGX: Peachtree City/atlanta, GA
+US: KVNX: Norman, OK
+US: KVBX: Vandenberg Afb: Orcutt, CA
+EU: Europe
+EU: GB: Great Brittain
+EU: SCAN: Scandinavia. Norway, Sweden And Denmark
+EU: ALPS: The Alps
+EU: NL: The Netherlands
+EU: DE: Germany
+EU: SP: Spain
+EU: FR: France
+EU: IT: Italy
+EU: PL: Poland
+EU: GR: Greece
+EU: TU: Turkey
+EU: RU: Russia
+EU: BA: Bahrain
+EU: BC: Botswana
+EU: SE: Republic of Seychelles
+EU: HU: Hungary
+EU: UK: Ukraine
+AF: AF: Africa
+AF: WA: West Africa
+AF: ZA: South Africa
+AF: DZ: Algeria
+AF: CE: Canary Islands
+AF: NG: Nigeria
+AF: TD: Chad
+AF: CG: Democratic Republic of Congo
+AF: EG: Egypt
+AF: ET: Ethiopia
+AF: CM: Cameroon
+AF: IS: Israel
+AF: LY: Libya
+AF: MG: Madagascar
+AF: MO: Morocco
+AF: BW: Namibia
+AF: SA: Saudi Arabia
+AF: SO: Somalia
+AF: SD: Sudan
+AF: TZ: Tanzania
+AF: TN: Tunisia
+AF: ZM: Zambia
+AF: KE: Kenya
+AF: AO: Angola
+DE: BAW: Baden-Württemberg
+DE: BAY: Bavaria
+DE: BBB: Berlin
+DE: BBB: Brandenburg
+DE: HES: Hesse
+DE: MVP: Mecklenburg-Western Pomerania
+DE: NIB: Lower Saxony
+DE: NIB: Bremen
+DE: NRW: North Rhine-Westphalia
+DE: RPS: Rhineland-Palatinate
+DE: RPS: Saarland
+DE: SAC: Saxony
+DE: SAA: Saxony-Anhalt
+DE: SHH: Schleswig-Holstein
+DE: SHH: Hamburg
+DE: THU: Thuringia" | dmenu -r -i -l 50 -p "Select a radar to use as default:" | tr "[:lower:]" "[:upper:]")"
+
+ # Ensure user did not escape.
+ [ -z "$chosen" ] && exit 1
+
+ # Set continent code and radar code.
+ continentcode=${chosen%%:*}
+ radarcode=${chosen#* } radarcode=${radarcode%:*}
+
+ # Print codes to $radarloc file.
+ printf "%s,%s\\n" "$continentcode" "$radarcode" >"$radarloc"
+}
+
+getdoppler() {
+ cont=$(cut -c -2 "$radarloc")
+ loc=$(cut -c 4- "$radarloc")
+ notify-send "🌦️ Doppler RADAR" "Pulling most recent Doppler RADAR for $loc."
+ case "$cont" in
+ "US") curl -sL "https://radar.weather.gov/ridge/standard/${loc}_loop.gif" >"$doppler" ;;
+ "EU") curl -sL "https://api.sat24.com/animated/${loc}/rainTMC/2/" >"$doppler" ;;
+ "AF") curl -sL "https://api.sat24.com/animated/${loc}/rain/2/" >"$doppler" ;;
+ "DE")
+ loc="$(echo "$loc" | tr "[:upper:]" "[:lower:]")"
+ curl -sL "https://www.dwd.de/DWD/wetter/radar/radfilm_${loc}_akt.gif" >"$doppler"
+ ;;
+ esac
+}
+
+showdoppler() { setsid -f mpv --no-osc --loop=inf --no-terminal "$doppler"; }
+
+case $BLOCK_BUTTON in
+1)
+ [ "$MANPAGER" = "less -s" ] && pager=true || pager=false
+ [ "$pager" = "false" ] && {
+ export MANPAGER='less -s'
+ export LESS="R"
+ export LESS_TERMCAP_mb="$(printf '%b' '')"
+ export LESS_TERMCAP_md="$(printf '%b' '')"
+ export LESS_TERMCAP_me="$(printf '%b' '')"
+ export LESS_TERMCAP_so="$(printf '%b' '')"
+ export LESS_TERMCAP_se="$(printf '%b' '')"
+ export LESS_TERMCAP_us="$(printf '%b' '')"
+ export LESS_TERMCAP_ue="$(printf '%b' '')"
+ export LESSOPEN="| /usr/bin/highlight -O ansi %s 2>/dev/null"
+ }
+ setsid -f "$TERMINAL" -e less -Sf "${XDG_CACHE_HOME:-${HOME}/.cache}/weatherreport"
+ [ "$pager" = "false" ] && {
+ export MANPAGER="sh -c 'col -bx | bat -l man -p'"
+ export MANROFFOPT="-c"
+ }
+ ;;
+2)
+ [ ! -f "$radarloc" ] && pickloc && getdoppler
+ [ $(($(date '+%s') - $(stat -c %Y "$doppler"))) -gt "$secs" ] && getdoppler
+ showdoppler
+ ;;
+3)
+ notify-send "🌈 Weather module (updates every 3 hours)" "\- Left click for full forecast
+- Shift + left click to update forecast
+🏂: Chance of snow (hidden if it's 0)
+☔: Chance of rain
+💧: Humidity (icon changes depending on the level)
+🎐: Wind speed
+🌞: Current (feel like) temperature
+🥶: Daily lowest temperature
+🥵: Daily highest temperature"
+ notify-send "$(todayweather)"
+ notify-send "🗺️ Doppler RADAR module" "\- Middle click for local Doppler RADAR
+- Shift + middle click to update RADAR location
+After $secs seconds, new clicks will also automatically update the doppler RADAR"
+ ;;
+6) getweatherreport && getweatherreportjson && notify-send "🌈 Updated forecast" ;;
+7) pickloc && getdoppler && showdoppler ;;
+8) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+checkforecast "$weatherreport" || { getweatherreport && pkill -RTMIN+15 "${STATUSBAR:-dwmblocks}" && sleep 3; }
+checkforecast "$weatherreportjson" || { getweatherreportjson && pkill -RTMIN+15 "${STATUSBAR:-dwmblocks}" && sleep 3; }
+showweather
diff --git a/ar/.local/bin/statusbar/sb-help-icon b/ar/.local/bin/statusbar/sb-help-icon
new file mode 100755
index 0000000..2717758
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-help-icon
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# The clickable help menu. Middle click to restart wm.
+
+# If dwm is running, use dwm's readme and restart.
+pidof dwm >/dev/null &&
+ READMEFILE=/usr/local/share/dwm/thesiah.mom
+restartwm() { pkill -HUP dwm; } ||
+ restartwm() { i3 restart; }
+
+case $BLOCK_BUTTON in
+1) groff -mom "${READMEFILE:-${XDG_DATA_HOME:-${HOME}/.local/share}/thesiah/thesiah.mom}" -Tpdf | zathura - ;;
+2) restartwm ;;
+3) notify-send "❓ Help module" "\- Left click to open THESIAH guide
+- Middle click to refresh window manager
+- Shift + Middle click to run system action
+- Shift + Right clict to lock screen" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+7) sysact ;;
+8) slock ;;
+esac
+echo "❓"
diff --git a/ar/.local/bin/statusbar/sb-internet b/ar/.local/bin/statusbar/sb-internet
new file mode 100755
index 0000000..3d4ff4e
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-internet
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# Show wifi 🛜 and percent strength or 📡 if none.
+# Show 🌐 if connected to ethernet or ❎ if none.
+# Show 🛰️ if a vpn connection is active
+
+case $BLOCK_BUTTON in
+1)
+ "$TERMINAL" -e nmtui
+ pkill -RTMIN+7 dwmblocks
+ ;;
+3) notify-send "🌐 Internet module" "\- Left click to connect
+❌: wifi disabled
+📡: no wifi connection
+🛜: wifi connection with quality
+❎: no ethernet
+🌐: ethernet working
+🛰️: vpn is active
+" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+# Wifi
+if [ "$(cat /sys/class/net/w*/operstate 2>/dev/null)" = 'up' ]; then
+ wifiicon="$(awk '/^\s*w/ { print "🛜" int($3 * 100 / 70) "%" }' /proc/net/wireless)"
+elif [ "$(cat /sys/class/net/w*/operstate 2>/dev/null)" = 'down' ]; then
+ [ "$(cat /sys/class/net/w*/flags 2>/dev/null)" = '0x1003' ] && wifiicon="📡" || wifiicon="❌"
+fi
+
+# Ethernet
+[ "$(cat /sys/class/net/e*/operstate 2>/dev/null)" = 'up' ] && ethericon="🌐" || ethericon="❎"
+
+# TUN
+[ -n "$(cat /sys/class/net/tun*/operstate 2>/dev/null)" ] && tunicon=" 🛰️"
+
+if [ "$(cat /sys/class/net/w*/operstate 2>/dev/null)" = 'up' ] && [ ! "$(cat /sys/class/net/e*/operstate 2>/dev/null)" = 'up' ]; then
+ printf "%s%s\n" "$wifiicon" "$tunicon"
+elif [ ! "$(cat /sys/class/net/w*/operstate 2>/dev/null)" = 'up' ] && [ "$(cat /sys/class/net/e*/operstate 2>/dev/null)" = 'up' ]; then
+ printf "%s%s\n" "$ethericon" "$tunicon"
+else
+ printf "%s%s%s\n" "$wifiicon" " $ethericon" "$tunicon"
+fi
diff --git a/ar/.local/bin/statusbar/sb-iplocate b/ar/.local/bin/statusbar/sb-iplocate
new file mode 100755
index 0000000..d84445e
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-iplocate
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Gets your public ip address checks which country you are in and
+# displays that information in the statusbar
+#
+# https://www.maketecheasier.com/ip-address-geolocation-lookups-linux/
+
+set -e
+
+ifinstalled "geoip"
+addr="$(geoiplookup "$(curl -sfm 1 ifconfig.me 2>/dev/null)")"
+name="${addr##*, }"
+flag="$(grep "flag: $name" "${XDG_DATA_HOME:-${HOME}/.local/share}/larbs/emoji")"
+flag="${flag%% *}"
+printf "%s %s\\n" "$flag" "$name"
diff --git a/ar/.local/bin/statusbar/sb-keyboard b/ar/.local/bin/statusbar/sb-keyboard
new file mode 100755
index 0000000..6329020
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-keyboard
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# works on any init system
+# requirements: dmenu, xorg-setxkbmap
+kb="$(setxkbmap -query | grep -oP 'layout:\s*\K\w+')" || exit 1
+
+case $BLOCK_BUTTON in
+1) fcitx5-remote -t && kill -44 "$(pidof "${STATUSBAR:-dwmblocks}")" ;;
+2)
+ kb_choice="$(awk '/! layout/{flag=1; next} /! variant/{flag=0} flag {print $2, "- " $1}' /usr/share/X11/xkb/rules/base.lst | dmenu -l 15)"
+ [ -z "$kb_choice" ] && exit 0
+ kb="$(echo "$kb_choice" | awk '{print $3}')"
+ setxkbmap "$kb"
+ pkill -RTMIN+10 "${STATUSBAR:-dwmblocks}"
+ ;;
+3) notify-send "⌨️ Input Method module" "\- Shows current input method (defalt US)
+- Left click to switch language (EN/KO)
+- Middle click to change keyboard" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+if [ "$kb" = "us" ] || [ "$kb" = "kr" ]; then
+ if [ "$(fcitx5-remote)" -eq 1 ]; then
+ echo "⌨️US"
+ elif [ "$(fcitx5-remote)" -eq 2 ]; then
+ case "$(fcitx5-remote -n)" in
+ *ko* | *Korean* | *hangul*) echo "⌨️KO" ;;
+ *) echo "⌨️$(setxkbmap -query | grep 'layout:' | sed 's/layout:\s*\(\S*\)/\1/g')" ;;
+ esac
+ else
+ echo "⌨️??"
+ fi
+else
+ echo "⌨️$kb"
+fi
diff --git a/ar/.local/bin/statusbar/sb-mailbox b/ar/.local/bin/statusbar/sb-mailbox
new file mode 100755
index 0000000..4496efc
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-mailbox
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Displays number of unread mail and an loading icon if updating.
+# When clicked, brings up `neomutt`.
+
+case $BLOCK_BUTTON in
+1)
+ setsid -w -f "$TERMINAL" -e neomutt
+ pkill -RTMIN+20 "${STATUSBAR:-dwmblocks}"
+ ;;
+2) setsid -f mw -Y >/dev/null ;;
+3) notify-send "📬 Mail module" "\- Shows unread mail
+- Shows 🔃 if syncing mail
+- Left click opens neomutt
+- Middle click syncs mail" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+unread="$(find "${XDG_DATA_HOME:-${HOME}/.local/share}"/mail/*/[Ii][Nn][Bb][Oo][Xx]/new/* -type f | wc -l 2>/dev/null)"
+
+pidof mbsync >/dev/null 2>&1 && icon="🔃"
+
+[ "$unread" = "0" ] && [ "$icon" = "" ] || echo "📬$unread$icon"
diff --git a/ar/.local/bin/statusbar/sb-memory b/ar/.local/bin/statusbar/sb-memory
new file mode 100755
index 0000000..bc7085a
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-memory
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+case $BLOCK_BUTTON in
+1) notify-send "🐏 Memory hogs" "$(
+ ps axch -o cmd:15,%mem --sort=-%mem | head
+ echo
+ free --mebi | sed -n '2{p;q}' | awk '{printf ("👟%2.2fGB/%2.2fGB\n", ( $3 / 1024), ($2 / 1024))}'
+)" ;;
+2) setsid -f "$TERMINAL" -e htop ;;
+3) notify-send "🐏 Memory module" "\- Shows Memory used/total
+- Left click to show memory hogs
+- Middle click to open htop" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+[ $(xrandr | grep "\*" | awk '{print $1}' | sed 's/x[0-9]*//g') -lt 1920 ] &&
+ free --mebi | sed -n '2{p;q}' | awk '{printf ("🐏%d%%\n", ($3/$2)*100+0.5 )}' ||
+ free --mebi | sed -n '2{p;q}' | awk '{printf ("🐏%dGB/%dGB", $3/1000+0.5,$2/1000+0.5)}'
diff --git a/ar/.local/bin/statusbar/sb-mpdup b/ar/.local/bin/statusbar/sb-mpdup
new file mode 100755
index 0000000..90e9c7e
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-mpdup
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# This loop will update the mpd statusbar module whenever a command changes the
+# music player's status. mpd must be running on X's start for this to work.
+
+while :; do
+ mpc idle >/dev/null && kill -57 "$(pidof "${STATUSBAR:-dwmblocks}")" || break
+done
diff --git a/ar/.local/bin/statusbar/sb-music b/ar/.local/bin/statusbar/sb-music
new file mode 100755
index 0000000..e47fb06
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-music
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+truncate_string() {
+ local input="$1"
+ local max_length="$2"
+ if [ "${#input}" -gt "$max_length" ]; then
+ echo "${input:0:$((max_length - 2))}.."
+ else
+ echo "$input"
+ fi
+}
+
+filter() {
+ if ps -C mpd >/dev/null 2>&1; then
+ screen_width=$(xrandr | grep '\*' | awk '{print $1}' | cut -d'x' -f1)
+ artist=$(mpc current -f %artist%)
+ title=$(mpc current -f %title%)
+
+ if [ "$screen_width" -le 2048 ]; then
+ max_length=$((screen_width / 100))
+ artist=$(truncate_string "$artist" "$max_length")
+ title=$(truncate_string "$title" "$max_length")
+ else
+ artist="$(mpc current -f %artist%)"
+ title="$(mpc current -f %title%)"
+ fi
+
+ case "$(mpc status %state%)" in
+ "playing") prefix="🎵" ;;
+ "paused") prefix="⏸" ;;
+ *) return ;;
+ esac
+
+ indicators=""
+ [ "$(mpc status %single%)" = "on" ] && indicators="${indicators}🔀"
+ [ "$(mpc status %random%)" = "on" ] && indicators="${indicators}🔂"
+ [ "$(mpc status %repeat%)" = "on" ] && indicators="${indicators}🔁"
+
+ sig=$(grep "${0##*/}" "${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwmblocks/config.h" | awk -F',' '{print $3}')
+ case $sig in
+ *0*)
+ echo "$prefix$artist - $title$([ -n "$indicators" ] && echo "$indicators")"
+ ;;
+ *1*)
+ echo "$prefix$artist - $title $(mpc status %currenttime%)/$(mpc status %totaltime%)$([ -n "$indicators" ] && echo "$indicators")"
+ ;;
+ esac
+ fi
+}
+
+[ "$(grep "${0##*/}" "${XDG_SOURCES_HOME:-${HOME}/.local/src}/suckless/dwmblocks/config.h" | awk -F',' '{print $3}')" -eq 0 ] && {
+ pidof -x sb-mpdup >/dev/null 2>&1 || sb-mpdup >/dev/null 2>&1 &
+}
+
+# Handling interaction based on button press
+case $BLOCK_BUTTON in
+1) # left click, opens ncmpcpp
+ mpc status | filter
+ setsid -f "$TERMINAL" -e ncmpcpp
+ ;;
+2) mpc toggle | filter ;; # middle click, pause/unpause
+3) # right click
+ notify-send "🎵 Music module" "\- Shows mpd song playing and status
+- 🎵 if playing
+- ⏸ if paused
+- 🔂 if single on
+- 🔁 if repeat on
+- 🔀 if random on
+- Left click opens ncmpcpp
+- Middle click pauses/unpause
+- Scroll changes track"
+ notify-send "🎵 $(mpc current)" "⏭️ $(mpc queued)"
+ ;;
+4) mpc prev | filter ;; # scroll up, previous
+5) mpc next | filter ;; # scroll down, next
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+*) mpc status | filter ;; # default, show current status
+esac
diff --git a/ar/.local/bin/statusbar/sb-nettraf b/ar/.local/bin/statusbar/sb-nettraf
new file mode 100755
index 0000000..7bba320
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-nettraf
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Module showing network traffic. Shows how much data has been received (RX) or
+# transmitted (TX) since the previous time this script ran. So if run every
+# second, gives network traffic per second.
+
+case $BLOCK_BUTTON in
+1) setsid -f "$TERMINAL" -e bmon ;;
+3) notify-send "🌐 Network traffic module" "🔻: Traffic received
+🔺: Traffic transmitted" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+update() {
+ sum=0
+ for arg; do
+ read -r i <"$arg"
+ sum=$((sum + i))
+ done
+ cache=/tmp/${1##*/}
+ [ -f "$cache" ] && read -r old <"$cache" || old=0
+ printf %d\\n "$sum" >"$cache"
+ printf %d\\n $((sum - old))
+}
+
+rx=$(update /sys/class/net/[ew]*/statistics/rx_bytes)
+tx=$(update /sys/class/net/[ew]*/statistics/tx_bytes)
+
+printf "🔻%4sB 🔺%4sB\\n" $(numfmt --to=iec $rx $tx)
diff --git a/ar/.local/bin/statusbar/sb-news b/ar/.local/bin/statusbar/sb-news
new file mode 100755
index 0000000..8bbbbf1
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-news
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Displays number of unread news items and an loading icon if updating.
+# When clicked, brings up `newsboat`.
+
+case $BLOCK_BUTTON in
+1) setsid "$TERMINAL" -e newsboat ;;
+2) setsid -f newsup >/dev/null && exit ;;
+3) notify-send "📰 News module" "\- Shows unread news items
+- Shows 🔃 if updating with \`newsup\`
+- Left click opens newsboat
+- Middle click syncs RSS feeds
+<b>Note:</b> Only one instance of newsboat (including updates) may be running at a time" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+cat /tmp/newsupdate 2>/dev/null || echo "$(newsboat -x print-unread | awk '{ if($1>0) print "📰" $1}')$(cat "${XDG_CONFIG_HOME:-${HOME}/.config}"/newsboat/.update 2>/dev/null)"
diff --git a/ar/.local/bin/statusbar/sb-packages b/ar/.local/bin/statusbar/sb-packages
new file mode 100755
index 0000000..5955c75
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-packages
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# Displays number of upgradeable packages.
+# For this to work, have a `pacman -Sy` command run in the background as a
+# cronjob every so often as root. This script will then read those packages.
+# When clicked, it will run an upgrade via pacman.
+#
+# Add the following text as a file in /usr/share/libalpm/hooks/statusbar.hook:
+#
+# [Trigger]
+# Operation = Upgrade
+# Type = Package
+# Target = *
+#
+# [Action]
+# Description = Updating statusbar...
+# When = PostTransaction
+# Exec = /usr/bin/pkill -RTMIN+16 dwmblocks # Or i3blocks if using i3.
+
+case $BLOCK_BUTTON in
+1) setsid -f "$TERMINAL" -e sb-popupgrade && remaps ;;
+2) notify-send "$(/usr/bin/pacman -Qu)" "$(/usr/bin/yay -Qu --aur)" ;;
+3) notify-send "🎁 Upgrade module" "📦: number of upgradable 'pacman' packages
+🧰: number of upgradable 'yay' packages
+- Left click to upgrade all packages
+- Middle click to show upgradable packages" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+pacnum=$(pacman -Qu | grep -Fcv "[ignored]" | sed "s/^/📦/;s/^📦0$//g")
+yaynum=$(yay -Qu --aur | grep -Fcv "[ignored]" | sed "s/^/🧰/;s/^🧰0$//g")
+upgradable=""
+[ -n "$pacnum" ] && upgradable="${upgradable}${pacnum} "
+[ -n "$yaynum" ] && upgradable="${upgradable}${yaynum} "
+upgradable=$(echo "$upgradable" | sed 's/ *$//')
+
+printf "%s\n" "$upgradable"
diff --git a/ar/.local/bin/statusbar/sb-popupgrade b/ar/.local/bin/statusbar/sb-popupgrade
new file mode 100755
index 0000000..14036eb
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-popupgrade
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+printf "Beginning upgrade\\n"
+
+yay -Syu
+pkill -RTMIN+16 "${STATUSBAR:-dwmblocks}"
+
+printf "\\nUpgrade complete.\\nPress <Enter> to exit window.\\n\\n"
+read -r _
diff --git a/ar/.local/bin/statusbar/sb-price b/ar/.local/bin/statusbar/sb-price
new file mode 100755
index 0000000..46731c2
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-price
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+# Usage:
+# price <currency-base currency> <name of currency> <icon> <signal>
+# price bat-btc "Basic Attention Token" 🦁 24
+# This will give the price of BAT denominated in BTC and will update on
+# signal 24.
+# When the name of the currency is multi-word, put it in quotes.
+
+[ -z "$1" ] && exit 1
+
+url="${CRYPTOURL:-rate.sx}"
+target="${1%%-*}"
+denom="${1##*-}"
+name="${2:-$1}"
+icon="${3:-💰}"
+case "$denom" in
+"$target" | usd)
+ denom="usd"
+ symb="$"
+ ;;
+gbp) symb="£" ;;
+eur) symb="€" ;;
+btc) symb="" ;;
+esac
+interval="@1d" # History contained in chart preceded by '@' (7d = 7 days)
+dir="${XDG_CACHE_HOME:-${HOME}/.cache}/crypto-prices"
+pricefile="$dir/$target-$denom"
+chartfile="$dir/$target-$denom-chart"
+filestat="$(stat -c %x "$pricefile" 2>/dev/null)"
+
+[ -d "$dir" ] || mkdir -p "$dir"
+
+updateprice() { curl -sf \
+ --fail-early "${denom}.${url}/1${target}" "${denom}.${url}/${target}${interval}" \
+ --output "$pricefile" --output "$chartfile" ||
+ rm -f "$pricefile" "$chartfile"; }
+
+[ "${filestat%% *}" != "$(date '+%Y-%m-%d')" ] &&
+ updateme="1"
+
+case $BLOCK_BUTTON in
+1) setsid "$TERMINAL" -e less -Srf "$chartfile" ;;
+2)
+ notify-send -u low "$icon Updating..." "Updating $name price..."
+ updateme="1"
+ showupdate="1"
+ ;;
+3)
+ uptime="$(date -d "$filestat" '+%D at %T' | sed "s|$(date '+%D')|Today|")"
+ notify-send "$icon $name module" "\- <b>Exact price: \$$(cat "$pricefile")</b>
+- Left click for chart of changes
+- Middle click to update
+- Shows 🔃 if updating prices
+- <b>Last updated:
+$uptime</b>"
+ ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+[ -n "$updateme" ] &&
+ updateprice "$target" &&
+ [ -n "$showupdate" ] &&
+ notify-send "$icon Update complete" "$name price is now
+\$$(cat "$pricefile")"
+
+[ -f "$pricefile" ] && printf "%s%s%0.2f" "$icon" "$symb" "$(cat "$pricefile")"
diff --git a/ar/.local/bin/statusbar/sb-queues b/ar/.local/bin/statusbar/sb-queues
new file mode 100755
index 0000000..7cd48a7
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-queues
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+notify_filenames() {
+ while IFS= read -r line; do
+ id=$(echo "$line" | awk '{print $1}')
+ url=$(tsp -l | awk -v id="$id" 'flag && /notify-send/ {print $0; flag=0} $1 == id {flag=1}' | grep -o 'https://[^\"]*')
+ if [ -n "$url" ]; then
+ decoded_url=$(echo "$url" | sed 's/&amp;/\&/g')
+ yt-dlp --no-playlist --simulate --get-filename "$decoded_url" 2>/dev/null | while IFS= read -r filename; do
+ notify-send "🔽 Downloading:" "$filename"
+ done
+ else
+ notify-send "🪹 No URL extracted for task $id"
+ fi
+ done <<EOF
+$(tsp -l | awk '/running/ && /yt-dlp/')
+EOF
+ if [ -z "$url" ]; then
+ notify-send "💤 No active yt-dlp downloads"
+ fi
+ pkill -RTMIN+21 "${STATUSBAR:-dwmblocks}"
+}
+
+# This block displays the number of running and queued background tasks. Requires tsp.
+num=$(tsp -l | awk -v numr=0 -v numq=0 '{if (!/notify-send/ && /running/) numr++; if (!/notify-send/ && /queued/) numq++} END{print numr"|"numq}')
+
+# Handle mouse clicks
+case $BLOCK_BUTTON in
+1) setsid -f "$TERMINAL" -e sh -c 'tsp -l; echo "\nPress enter to close..." ; read REPLY' ;;
+2) setsid -f "$TERMINAL" -e sh -c 'tsp -t' ;;
+3)
+ notify_filenames
+ notify-send "📝 Tasks module" "🤖: number of running/queued background tasks
+- Left click to show all tasks
+- Middle click to show the current task progress"
+ ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+cat /tmp/qplaylist 2>/dev/null || ([ "$num" != "0|0" ] && echo "🤖$num")
diff --git a/ar/.local/bin/statusbar/sb-repos b/ar/.local/bin/statusbar/sb-repos
new file mode 100755
index 0000000..1dad9f1
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-repos
@@ -0,0 +1,130 @@
+#!/bin/bash
+
+pidof transmission-daemon >/dev/null && exit 1
+
+# Directories containing Git repositories
+DOTFILES_REPOS="$HOME/.dotfiles"
+SUCKLESS_REPOS="$HOME/.local/src/suckless"
+PRIVATE_REPOS="$HOME/Private/repos"
+PUBLIC_REPOS="$HOME/Public/repos"
+
+# Icon indicators
+DOTFILES_ICON="⚙️"
+SUCKLESS_ICON="🛠"
+PRIVATE_ICON="🏠"
+PUBLIC_ICON="🏢"
+
+# Function to parse Git status and format symbols
+get_git_status_symbols() {
+ git status --porcelain | awk '
+ {
+ if ($1 == "??") {
+ changes["?"]++
+ } else if ($1 ~ /^[MADR]$/) {
+ changes[$1]++
+ }
+ }
+ END {
+ for (change in changes) {
+ printf "%s%s", change, changes[change]
+ }
+ }'
+}
+
+# Function to check for unpushed commits
+get_unpushed_commits() { git cherry -v 2>/dev/null | wc -l; }
+
+# Function to check for unpulled commits
+get_unpulled_commits() { git rev-list --count HEAD..@{upstream} 2>/dev/null; }
+
+# Function to check Git repository status for multiple repos
+check_multi_repo_status() {
+ local dir="$1"
+ local icon="$2"
+ local status=""
+ local changed_repos=""
+
+ while IFS= read -r git_dir; do
+ local repo_dir="${git_dir%/.git}"
+ cd "$repo_dir" || continue
+
+ changes=$(get_git_status_symbols)
+ unpushed=$(get_unpushed_commits)
+ unpulled=$(get_unpulled_commits)
+
+ if [ -n "$changes" ] || [ "$unpushed" -gt 0 ] || [ "$unpulled" -gt 0 ]; then
+ status+="$icon$changes"
+ [ "$unpushed" -gt 0 ] && status+="↑$unpushed"
+ [ "$unpulled" -gt 0 ] && status+="↓$unpulled"
+ status+=" "
+ changed_repos+="$repo_dir"
+ fi
+ done < <(find "$dir" -mindepth 2 -maxdepth 2 -type d -name ".git" 2>/dev/null)
+
+ printf "%s%s" "$status" "$changed_repos"
+}
+
+# Function to check Git repository status for a single repository
+check_single_repo_status() {
+ local dir="$1"
+ local icon="$2"
+ local repo_status=""
+ local changed_repo=""
+
+ if [ -d "$dir/.git" ]; then
+ cd "$dir" || return
+
+ changes=$(get_git_status_symbols)
+ unpushed=$(get_unpushed_commits)
+ unpulled=$(get_unpulled_commits)
+
+ if [ -n "$changes" ] || [ "$unpushed" -gt 0 ] || [ "$unpulled" -gt 0 ]; then
+ repo_status+="$icon$changes"
+ [ "$unpushed" -gt 0 ] && repo_status+="↑$unpushed"
+ [ "$unpulled" -gt 0 ] && repo_status+="↓$unpulled"
+ repo_status+=" "
+ changed_repo="$dir"
+ fi
+ fi
+
+ printf "%s%s" "$repo_status" "$changed_repo"
+}
+
+# Check statuses for repositories
+dotfiles_status=$(check_single_repo_status "$DOTFILES_REPOS" "$DOTFILES_ICON" | awk -F' ' '{print $1}')
+dotfiles_changes=$(check_single_repo_status "$DOTFILES_REPOS" "$DOTFILES_ICON" | awk -F' ' '{print $2}')
+
+suckless_status=$(check_single_repo_status "$SUCKLESS_REPOS" "$SUCKLESS_ICON" | awk -F' ' '{print $1}')
+suckless_changes=$(check_single_repo_status "$SUCKLESS_REPOS" "$SUCKLESS_ICON" | awk -F' ' '{print $2}')
+
+private_status=$(check_multi_repo_status "$PRIVATE_REPOS" "$PRIVATE_ICON" | awk -F' ' '{print $1}')
+private_changes=$(check_multi_repo_status "$PRIVATE_REPOS" "$PRIVATE_ICON" | awk -F' ' '{print $2}')
+
+public_status=$(check_multi_repo_status "$PUBLIC_REPOS" "$PUBLIC_ICON" | awk -F' ' '{print $1}')
+public_changes=$(check_multi_repo_status "$PUBLIC_REPOS" "$PUBLIC_ICON" | awk -F' ' '{print $1}')
+
+[ -f /tmp/gitsync ] && rm -f /tmp/gitsync
+
+# Combine statuses
+output=""
+[ -n "$dotfiles_status" ] && output+="$dotfiles_status "
+[ -n "$suckless_status" ] && output+="$suckless_status "
+[ -n "$private_status" ] && output+="$private_status "
+[ -n "$public_status" ] && output+="$public_status "
+
+# Trim trailing spaces and display output
+output="${output%"${output##*[! ]}"}"
+[ -n "$output" ] && (cat /tmp/gitsync 2>/dev/null || echo "$output")
+
+openrepos() {
+ all_changed_repos="$dotfiles_changes"$'\n'"$suckless_changes"$'\n'"$private_changes"$'\n'"$public_changes"
+ [ -n "$all_changed_repos" ] && exec "$TERMINAL" -e opensessions "$(echo "$all_changed_repos" | grep -v '^$')"
+}
+
+# Handle button actions
+case "$BLOCK_BUTTON" in
+1) openrepos ;;
+3) notify-send " Git module" "\- Shows git repositories changes
+- Left click opens changed repositories" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; # Launch editor for the script
+esac
diff --git a/ar/.local/bin/statusbar/sb-tasks b/ar/.local/bin/statusbar/sb-tasks
new file mode 100755
index 0000000..66be81b
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-tasks
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+# Get the current time and the time in one hour
+now=$(date +%s)
+in_one_hour=$(date -d "+1 hour" +%s)
+
+# Check for tasks due in the next hour
+tasks_due_today=$(task due:tomorrow _ids)
+tasks_due_count=0
+tasks_due_list=""
+
+for task_id in $tasks_due_today; do
+ task_due_date=$(task _get "$task_id".due)
+ task_due_epoch=$(date -d "$task_due_date" +%s)
+ task_description=$(task _get "$task_id".description)
+
+ # Check if the task is due within the next hour
+ if [ "$task_due_epoch" -gt "$now" ] && [ "$task_due_epoch" -le "$in_one_hour" ]; then
+ tasks_due_list="$tasks_due_list- $task_description\n"
+ tasks_due_count=$((tasks_due_count + 1))
+ fi
+done
+
+# Check for overdue tasks (tasks with due date in the past)
+overdue_tasks=$(task +OVERDUE _ids)
+overdue_count=0
+overdue_list=""
+
+for task_id in $overdue_tasks; do
+ task_description=$(task _get "$task_id".description)
+ overdue_list="$overdue_list- $task_description\n"
+ overdue_count=$((overdue_count + 1))
+done
+
+# Check for follow-up tasks
+follow_up_tasks=$(task follow.is:Y _ids -PARENT)
+follow_up_count=0
+follow_up_list=""
+
+for task_id in $follow_up_tasks; do
+ task_due_date=$(task _get "$task_id".due)
+ task_due_epoch=$(date -d "$task_due_date" +%s)
+ task_description=$(task _get "$task_id".description)
+
+ # Ensure that follow-up tasks are only shown if they are not overdue
+ if [ "$task_due_epoch" -ge "$now" ]; then
+ follow_up_list="$follow_up_list- $task_description\n"
+ follow_up_count=$((follow_up_count + 1))
+ fi
+done
+
+check_task_sync() {
+ if [ "$(task _get tw.syncneeded)" -eq 1 ]; then
+ tasks-sync
+ notify-send "📚 Tasks synced"
+ fi
+}
+
+# Handle mouse clicks
+case $BLOCK_BUTTON in
+1) # Combine actions for button 1 and button 2
+ notify-send "📚 Follow-up task(s) to complete:" "$(printf "%b" "$follow_up_list")
+📕 Tasks due in the next hour:
+$(printf "%b" "$tasks_due_list")
+⏰ Overdue Tasks:
+$(printf "%b" "$overdue_list")"
+ ;;
+2) check_task_sync ;;
+3)
+ notify-send "🗂️ Task Module" "Shows task counts.
+- Left click: Show tasks due soon.
+- Middle click: Show follow-up tasks."
+ ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+# Output task information to dwmblocks
+output=""
+
+[ "$overdue_count" -gt 0 ] && output="${output}⏰$overdue_count "
+[ "$follow_up_count" -gt 0 ] && output="${output}📚$follow_up_count "
+[ "$tasks_due_count" -gt 0 ] && output="${output}📕$tasks_due_count " && notify-send -u critical "🚑 Tasks Remained!" "$tasks_due_list"
+[ "$(task _get tw.syncneeded)" -eq 1 ] && output="${output}📑 "
+echo "${output%* }"
diff --git a/ar/.local/bin/statusbar/sb-torrent b/ar/.local/bin/statusbar/sb-torrent
new file mode 100755
index 0000000..79da88d
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-torrent
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+status=$(transmission-remote -l | grep % |
+ sed " # The letters are for sorting and will not appear
+ s/.*Stopped.*/A 🛑/;
+ s/.*Seeding.*/Z 🌱/;
+ s/.*100%.*/N ✅/;
+ s/.*Idle.*/B 🕰️/;
+ s/.*Uploading.*/L 🔼/;
+ s/.*%.*/M 🔽/" |
+ sort -h | uniq -c | awk '{print $3 $1}' | paste -sd ' ' -)
+
+if [ -z "$status" ]; then
+ echo "$status"
+else
+ pidof transmission-daemon >/dev/null && echo "🌲"
+fi
+
+case $BLOCK_BUTTON in
+1) setsid -f "$TERMINAL" -e stig ;;
+2) td-toggle ;;
+3) notify-send "🌱 Torrent module" "\- Left click to open stig
+- Middle click to toggle transmission
+- Shift click to edit script
+Module shows number of torrents:
+🛑: paused
+🕰: idle (seeds needed)
+🔼: uploading (unfinished)
+🔽: downloading
+✅: done
+🌱: done and seeding" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
diff --git a/ar/.local/bin/statusbar/sb-volume b/ar/.local/bin/statusbar/sb-volume
new file mode 100755
index 0000000..800f19e
--- /dev/null
+++ b/ar/.local/bin/statusbar/sb-volume
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Prints the current volume or 🔇 if muted.
+
+case $BLOCK_BUTTON in
+1)
+ setsid -w -f "$TERMINAL" -e pulsemixer
+ pkill -RTMIN+5 "${STATUSBAR:-dwmblocks}"
+ ;;
+2) wpctl set-mute @DEFAULT_SINK@ toggle ;;
+4) wpctl set-volume @DEFAULT_SINK@ 1%+ ;;
+5) wpctl set-volume @DEFAULT_SINK@ 1%- ;;
+3) notify-send "📢 Volume module" "\- Shows volume 🔊, 🔇 if muted
+- Middle click to mute
+- Scroll to change" ;;
+6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
+esac
+
+vol="$(wpctl get-volume @DEFAULT_AUDIO_SINK@)"
+
+# If muted, print 🔇 and exit.
+[ "$vol" != "${vol%\[MUTED\]}" ] && echo 🔇 && exit
+
+vol="${vol#Volume: }"
+
+split() {
+ # For ommiting the . without calling and external program.
+ IFS=$2
+ set -- $1
+ printf '%s' "$@"
+}
+
+vol="$(printf "%.0f" "$(split "$vol" ".")")"
+
+case 1 in
+$((vol > 100))) icon="📢" ;;
+$((vol >= 70))) icon="🔊" ;;
+$((vol >= 30))) icon="🔉" ;;
+$((vol >= 1))) icon="🔈" ;;
+*) echo 🔇 && exit ;;
+esac
+
+echo "$icon$vol%"
diff --git a/ar/.local/bin/stw b/ar/.local/bin/stw
new file mode 100755
index 0000000..2e221d1
--- /dev/null
+++ b/ar/.local/bin/stw
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+# Directory where your stow packages are located, adjust as necessary
+stowdir="${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}"
+
+# Function to list and select stow packages using dmenu
+select_stow_package() {
+ find "$stowdir" -mindepth 1 -maxdepth 1 -type d -not -name ".*" -not -name "default" | while read -r dir; do
+ if [ -n "$(find "$dir" -mindepth 1 -maxdepth 1)" ]; then
+ basename "$dir"
+ fi
+ done | dmenu -i -p "Select package to stow: "
+}
+
+# Function to ask user for resolution strategy using dmenu
+ask_resolution_strategy() {
+ printf "delete\nmove" | dmenu -i -p "Choose resolution strategy: "
+}
+
+# Function to stow a package and resolve conflicts
+stow_package() {
+ target="$1"
+ resolve_strategy="$2"
+ # Attempt to stow the package
+ output=$(stow --no-folding -S "$target" 2>&1)
+ status=$?
+
+ # Handle conflicts based on resolution strategy
+ if [ $status -ne 0 ]; then
+ echo "$output" | grep "over existing target is stowed to a different package" | while IFS= read -r line; do
+ conflict_path=$(echo "$line" | sed -E 's/.*\: (.*) =>.*/\1/')
+ full_path="$HOME/$conflict_path"
+ if [ "$resolve_strategy" = "delete" ]; then
+ rm -rf "$full_path"
+ elif [ "$resolve_strategy" = "move" ]; then
+ mv "$full_path" "${full_path}.dotbak"
+ fi
+ done
+ echo "$output" | grep "over existing target" | while IFS= read -r line; do
+ conflict_path=$(echo "$line" | sed -E 's/.*over existing target\s(.*)\ssince.*/\1/')
+ full_path="$HOME/$conflict_path"
+ if [ "$resolve_strategy" = "delete" ]; then
+ rm -rf "$full_path"
+ elif [ "$resolve_strategy" = "move" ]; then
+ mv "$full_path" "${full_path}.dotbak"
+ fi
+ done
+
+ # Retry stowing after conflict resolution
+ output=$(stow --no-folding -S "$target" 2>&1)
+ status=$?
+ fi
+}
+
+mail_sync() {
+ [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/mutt/muttrc" ] && [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/mutt/thesiah.muttrc" ] &&
+ ! grep -qF "source /home/$USER/.config/mutt/thesiah.muttrc" "${XDG_CONFIG_HOME:-${HOME}/.config}/mutt/muttrc" &&
+ sed -i "/source \/usr\/share\/mutt-wizard\/mutt-wizard\.muttrc/a source \/home\/$USER\/.config\/mutt\/thesiah\.muttrc" /home/"$USER"/.config/mutt/muttrc
+}
+
+# Ensure running from the correct directory
+cd "$stowdir" || exit 1
+
+# Select a stow package
+targetdir=$(select_stow_package) || exit 1
+
+# Ask the user for the resolution strategy
+resolve=$(ask_resolution_strategy) || exit 1
+
+stow_package "$targetdir" "$resolve" && stow_package "default" "$resolve" || exit
+ln -sf "$stowdir/$targetdir/.config/shell/profile" "$HOME/.zprofile"
+
+# Link for bash
+ln -sf "$stowdir/$targetdir/.config/bash/bash_profile" "$HOME/.bash_profile"
+ln -sf "$stowdir/$targetdir/.config/bash/bashrc" "$HOME/.bashrc"
+
+# Link for cro# Link for gtk 2.0
+ln -sf "$stowdir/$targetdir/.config/gtk-2.0/gtkrc-2.0" "$HOME/.gtkrc-2.0"
+
+# Sync mail
+mail_sync
+
+# Reload shortcuts (assumes this functionality is defined elsewhere and works as expected)
+shortcuts >/dev/null 2>&1 || exit 1
+zsh -c "source '${XDG_CONFIG_HOME:-${HOME}/.config}/shell/shortcutrc'" 2>/dev/null || exit 1
+zsh -c "source '${XDG_CONFIG_HOME:-${HOME}/.config}/shell/zshnameddirrc'" 2>/dev/null || exit 1
+notify-send "✅ Updated shortcuts"
diff --git a/ar/.local/bin/syncdot b/ar/.local/bin/syncdot
new file mode 100755
index 0000000..5e560e9
--- /dev/null
+++ b/ar/.local/bin/syncdot
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+# Define ANSI color codes for better readability in script output
+GREEN='\033[1;32m'
+BLUE='\033[1;34m'
+RED='\033[1;31m'
+NC='\033[0m' # No Color
+
+# Ensure the script exits on any error
+set -e
+
+# Navigate to the dotfiles directory
+cd "${XDG_DOTFILES_DIR:-${HOME}/.dotfiles}" || exit
+
+echo "${BLUE}Stashing existing changes...${NC}"
+stash_result=$(git stash push -m "sync-dotfiles: Before syncing dotfiles" 2>&1)
+needs_pop=0
+if ! echo "$stash_result" | grep -q "No local changes to save"; then
+ needs_pop=1
+fi
+
+echo "${BLUE}Pulling updates from dotfiles repo...${NC}"
+git pull origin master
+
+if [ "$needs_pop" -eq 1 ]; then
+ echo "${BLUE}Popping stashed changes...${NC}"
+ git stash pop
+fi
+
+# Check for merge conflicts after attempting to pop the stash
+unmerged_files=$(git diff --name-only --diff-filter=U)
+if [ -n "$unmerged_files" ]; then
+ echo "${RED}The following files have merge conflicts after popping the stash:${NC}"
+ printf "%s\n" "$unmerged_files"
+ exit 1
+else
+ echo "${GREEN}No merge conflicts detected. Proceeding with dotfiles linkage...${NC}"
+ # Ensure GNU Stow is installed
+ if ! command -v stow >/dev/null 2>&1; then
+ echo "${RED}GNU Stow not found. Please install GNU Stow to continue.${NC}"
+ exit 1
+ fi
+ # Run stow to ensure all new dotfiles are linked, targeting directories named after the OS
+ stow -v --no-folding -S "$(whereami)" # Verbose output
+ echo "${GREEN}Dotfiles successfully linked.${NC}"
+fi
diff --git a/ar/.local/bin/synctime b/ar/.local/bin/synctime
new file mode 100755
index 0000000..47f4310
--- /dev/null
+++ b/ar/.local/bin/synctime
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+select_timezone() {
+ initial_path="/usr/share/zoneinfo"
+ tz_selection=$initial_path
+
+ while [ -d "$tz_selection" ]; do
+ selection=$(find "$tz_selection" -mindepth 1 -maxdepth 1 | sed "s|^$initial_path/||" | dmenu -i -l 20 -p "Select Time Zone:") || exit 1
+ tz_selection="$initial_path/$selection"
+ done
+
+ echo "${tz_selection#$initial_path/}"
+}
+
+current_zone=$(select_timezone) || exit 1
+sudo ln -sf /usr/share/zoneinfo/$current_zone /etc/localtime || exit 1
+sudo hwclock --systohc
+
+echo "Current timezone: $current_zone"
+date
+
+pkill -RTMIN+3 ${STATUSBAR:-dwmblocks}
diff --git a/ar/.local/bin/sysact b/ar/.local/bin/sysact
new file mode 100755
index 0000000..3706d2b
--- /dev/null
+++ b/ar/.local/bin/sysact
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# A dmenu wrapper script for system functions.
+export WM="dwm"
+case "$(readlink -f /sbin/init)" in
+*systemd*) ctl='systemctl' ;;
+*) ctl='loginctl' ;;
+esac
+
+wmpid() { # This function is needed if there are multiple instances of the window manager.
+ tree="$(pstree -ps $$)"
+ tree="${tree#*$WM(}"
+ echo "${tree%%)*}"
+}
+
+case "$(printf "🔒 lock\n🚪 leave $WM\n♻️ renew $WM\n🐻 hibernate\n🔃 reboot\n🖥️shutdown\n💤 sleep\n📺 display off" | dmenu -i -p 'Action: ')" in
+'🔒 lock') slock ;;
+"🚪 leave $WM") kill -TERM "$(wmpid)" ;;
+"♻️ renew $WM") kill -HUP "$(wmpid)" ;;
+'🐻 hibernate') slock $ctl hibernate -i ;;
+'💤 sleep') slock $ctl suspend -i ;;
+'🔃 reboot') $ctl reboot -i ;;
+'🖥️shutdown') $ctl poweroff -i ;;
+'📺 display off') xset dpms force off ;;
+*) exit 1 ;;
+esac
diff --git a/ar/.local/bin/tablet b/ar/.local/bin/tablet
new file mode 100755
index 0000000..1b4e556
--- /dev/null
+++ b/ar/.local/bin/tablet
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+# Find the line in "xrandr -q --verbose" output that contains current screen orientation and "strip" out current orientation.
+rotation="$(xrandr -q --verbose | grep 'connected' | grep -Eo '\) (normal|left|inverted|right) \(' | grep -Eo '(normal|left|inverted|right)')"
+penstylus="$(xsetwacom list devices | grep 'Pen' | grep 'stylus' | sed 's/\s*id.*//g')"
+penerase="$(xsetwacom list devices | grep 'Pen' | grep 'erase' | sed 's/\s*id.*//g')"
+fingertouch="$(xsetwacom list devices | grep 'Finger' | grep 'touch' | sed 's/\s*id.*//g')"
+
+# Using current screen orientation proceed to rotate screen and input tools.
+case "$rotation" in
+normal)
+ # rotate to the left
+ xrandr -o left
+ xsetwacom set "$penstylus" rotate ccw
+ xsetwacom set "$penerase" rotate ccw
+ xsetwacom set "$fingertouch" rotate ccw
+ ;;
+left)
+ # rotate to normal
+ xrandr -o inverted
+ xsetwacom set "$penstylus" rotate half
+ xsetwacom set "$penerase" rotate half
+ xsetwacom set "$fingertouch" rotate half
+ ;;
+inverted)
+ # rotate to normal
+ xrandr -o right
+ xsetwacom set "$penstylus" rotate cw
+ xsetwacom set "$penerase" rotate cw
+ xsetwacom set "$fingertouch" rotate cw
+ ;;
+right)
+ # rotate to normal
+ xrandr -o normal
+ xsetwacom set "$penstylus" rotate none
+ xsetwacom set "$penerase" rotate none
+ xsetwacom set "$fingertouch" rotate none
+ ;;
+esac
diff --git a/ar/.local/bin/tag b/ar/.local/bin/tag
new file mode 100755
index 0000000..8abce0f
--- /dev/null
+++ b/ar/.local/bin/tag
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+err() { echo "Usage:
+ tag [OPTIONS] file
+Options:
+ -a: artist/author
+ -t: song/chapter title
+ -A: album/book title
+ -n: track/chapter number
+ -N: total number of tracks/chapters
+ -d: year of publication
+ -g: genre
+ -c: comment
+You will be prompted for title, artist, album and track if not given." && exit 1; }
+
+while getopts "a:t:A:n:N:d:g:c:" o; do case "${o}" in
+ a) artist="${OPTARG}" ;;
+ t) title="${OPTARG}" ;;
+ A) album="${OPTARG}" ;;
+ n) track="${OPTARG}" ;;
+ N) total="${OPTARG}" ;;
+ d) date="${OPTARG}" ;;
+ g) genre="${OPTARG}" ;;
+ c) comment="${OPTARG}" ;;
+ *) printf "Invalid option: -%s\\n" "$OPTARG" && err ;;
+ esac done
+
+shift $((OPTIND - 1))
+
+file="$1"
+
+temp="$(mktemp -p "$(dirname "$file")")"
+trap 'rm -f $temp' HUP INT QUIT TERM PWR EXIT
+
+[ ! -f "$file" ] && echo 'Provide file to tag.' && err
+
+[ -z "$title" ] && echo 'Enter a title.' && read -r title
+[ -z "$artist" ] && echo 'Enter an artist.' && read -r artist
+[ -z "$album" ] && echo 'Enter an album.' && read -r album
+[ -z "$track" ] && echo 'Enter a track number.' && read -r track
+
+cp -f "$file" "$temp" && ffmpeg -i "$temp" -map 0 -y -codec copy \
+ -metadata title="$title" \
+ -metadata album="$album" \
+ -metadata artist="$artist" \
+ -metadata track="${track}${total:+/"$total"}" \
+ ${date:+-metadata date="$date"} \
+ ${genre:+-metadata genre="$genre"} \
+ ${comment:+-metadata comment="$comment"} "$file"
diff --git a/ar/.local/bin/task/taskwarrior-tui/annotate-with-new-note b/ar/.local/bin/task/taskwarrior-tui/annotate-with-new-note
new file mode 100755
index 0000000..3c67d24
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/annotate-with-new-note
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# UUID of the task to annotate
+uuid="$*"
+
+# Directory where notes are stored
+notes_dir="$HOME/Private/repos/Obsidian/SI/Notes"
+templates_dir="$HOME/Private/repos/Obsidian/SI/Resources/Templates"
+
+# Prompt for the new note name
+printf "Enter the name for the new note: "
+read -r new_note_name
+
+copy_note="$templates_dir/projects.md"
+filepath="$notes_dir/$new_note_name.md"
+
+# Check if a file with this name already exists
+if [ -f "$filepath" ]; then
+ echo "File with this name already exists. Annotating the task with the existing note."
+else
+ nvim -n -c "ObsidianNew $new_note_name" --headless >/dev/null 2>&1 &
+ if [ -f "$copy_note" ]; then
+ cp "$copy_note" "$filepath"
+ echo "New note created and opened in Neovim."
+ fi
+fi
+
+# Annotate the task with the filepath
+task_output=$(task rc.bulk=0 rc.confirmation=off "$uuid" annotate "$filepath")
+
+# Check if annotation was successful
+case "$task_output" in
+*"Annotated"*)
+ echo "Successfully annotated the task with the note."
+ ;;
+*)
+ echo "Failed to annotate the task."
+ ;;
+esac
+
+${EDITOR:-nvim} "$filepath"
diff --git a/ar/.local/bin/task/taskwarrior-tui/annotate-with-note b/ar/.local/bin/task/taskwarrior-tui/annotate-with-note
new file mode 100755
index 0000000..c744314
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/annotate-with-note
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# UUID of the task to annotate
+uuid="$*"
+
+# Base directory where notes are stored
+notes_dir="$HOME/Private/repos/Obsidian/SI/Notes"
+
+# List of subdirectories to search in
+subdirs="areas meetings projects resources reviews"
+
+# Construct the find command with the subdirectories
+search_paths=""
+for subdir in $subdirs; do
+ search_paths="$search_paths $notes_dir/$subdir"
+done
+
+# Find files in the specified subdirectories and show fzf dialog to select an existing note
+filepath=$(find "$search_paths" -type f -name '*.md' | fzf --preview "bat --color=always {}")
+
+# If fzf was cancelled, exit the script
+if [ -z "$filepath" ]; then
+ echo "No file selected. Exiting."
+ exit 1
+fi
+
+# Annotate the task with the selected filepath
+task_output=$(task rc.bulk=0 rc.confirmation=off "$uuid" annotate "$filepath")
+
+# Check if annotation was successful
+case "$task_output" in
+*"Annotated"*)
+ echo "Successfully annotated the task with the note."
+ ;;
+*)
+ echo "Failed to annotate the task."
+ ;;
+esac
+
+# Open the selected note in nvim
+${EDITOR:-nvim} "$filepath"
diff --git a/ar/.local/bin/task/taskwarrior-tui/cycle-priority b/ar/.local/bin/task/taskwarrior-tui/cycle-priority
new file mode 100755
index 0000000..43bc91e
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/cycle-priority
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+uuid="$1"
+
+# Get the current priority of the task
+current_priority=$(task _get $uuid.priority)
+
+# Cycle through the priorities: H -> M -> L -> (unset) -> H
+case "$current_priority" in
+H)
+ new_priority="M"
+ echo "Switching priority from 'H' to 'M'"
+ ;;
+M)
+ new_priority="L"
+ echo "Switching priority from 'M' to 'L'"
+ ;;
+L)
+ new_priority=""
+ echo "Switching priority from 'L' to (no priority)"
+ ;;
+"")
+ new_priority="H"
+ echo "Switching priority from (no priority) to 'H'"
+ ;;
+esac
+
+# Update the task with the new priority value, or remove priority if it's an empty string
+if [ -n "$new_priority" ]; then
+ echo "Updating task $uuid with new priority: $new_priority"
+ task rc.bulk=0 rc.confirmation=off "$uuid" modify priority="$new_priority"
+else
+ echo "Removing priority from task $uuid"
+ task rc.bulk=0 rc.confirmation=off "$uuid" modify priority:
+fi
diff --git a/ar/.local/bin/task/taskwarrior-tui/cycle-tmux-projects b/ar/.local/bin/task/taskwarrior-tui/cycle-tmux-projects
new file mode 100755
index 0000000..360dc20
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/cycle-tmux-projects
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Get the UUID of the selected task from the arguments
+uuid="$*"
+
+# Fetch the project of the task using Taskwarrior and jq
+project=$(task "$uuid" export | jq -r '.[0].project')
+
+# Check if the project is empty
+if [ -z "$project" ] || [ "$project" = "null" ]; then
+ echo "No project found for the selected task."
+ # Optionally, reset the filter in taskwarrior-tui
+ tmux send-keys "/" Escape
+ tmux send-keys Escape
+ tmux send-keys "/"
+ tmux send-keys Enter
+ exit 0
+fi
+
+# Escape any special characters in the project name for use in tmux command
+escaped_project=$(printf '%s' "$project" | sed 's/[][\.*^$(){}+?|]/\\&/g')
+
+# Send keys via tmux to apply the filter in taskwarrior-tui
+tmux send-keys "/"
+tmux send-keys Escape
+tmux send-keys Escape
+tmux send-keys "/"
+
+# Use exact match in the filter to avoid partial matches
+tmux send-keys "project.is:$escaped_project"
+tmux send-keys Enter
diff --git a/ar/.local/bin/task/taskwarrior-tui/decrease-priority b/ar/.local/bin/task/taskwarrior-tui/decrease-priority
new file mode 100755
index 0000000..b2b0508
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/decrease-priority
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+uuid="$1"
+current_priority=$(task _get "$uuid".manual_priority)
+
+# Check if manual_priority is set; if not, initialize to 0
+if [ -z "$current_priority" ]; then
+ current_priority=0
+fi
+
+# Decrement the priority using expr
+new_priority=$((current_priority - 1))
+
+# Update the task with the new manual_priority value
+task rc.bulk=0 rc.confirmation=off "$uuid" modify manual_priority="$new_priority"
diff --git a/ar/.local/bin/task/taskwarrior-tui/git-issue-sync b/ar/.local/bin/task/taskwarrior-tui/git-issue-sync
new file mode 100755
index 0000000..da01bc4
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/git-issue-sync
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+# github_issue_sync.sh
+# Synchronizes GitHub and Linear issues with Taskwarrior
+
+# Set new line and tab for word splitting
+IFS='
+ '
+
+# Logger with timestamp
+log() {
+ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*"
+}
+
+# Function to trim leading and trailing whitespace using sed
+trim_whitespace() {
+ input="$1"
+ echo "$input" | sed 's/^[ \t]*//;s/[ \t]*$//'
+}
+
+# Validate necessary environment variables
+validate_env_vars() {
+ oldIFS="$IFS"
+ IFS=' '
+ required_vars="api userid"
+ for var in $required_vars; do
+ val=$(pass show "api/linear/$var")
+ if [ -z "$val" ]; then
+ echo "Error: Environment variable $var is not set." >&2
+ exit 1
+ fi
+ done
+ IFS="$oldIFS"
+}
+
+# Retrieve and format GitHub issues
+get_github_issues() {
+ issues=$(gh api -X GET /search/issues \
+ -f q='is:issue is:open assignee:TheSiahxyz' \
+ --jq '.items[] | {id: .number, description: .title, repository: .repository_url, html_url: .html_url}') || {
+ echo "Error: Unable to fetch GitHub issues" >&2
+ return 1
+ }
+ echo "$issues"
+}
+
+# Retrieve and format Linear issues
+get_linear_issues() {
+ issues=$(curl -s -X POST \
+ -H "Content-Type: application/json" \
+ -H "Authorization: $LINEAR_API_KEY" \
+ --data '{"query": "query { user(id: \"'"$LINEAR_USER_ID"'\") { id name assignedIssues(filter: { state: { name: { nin: [\"Released\", \"Canceled\"] } } }) { nodes { id title url } } } }"}' \
+ https://api.linear.app/graphql | jq -c '.data.user.assignedIssues.nodes[] | {id: .id, description: .title, repository: "linear", html_url: .url}') || {
+ echo "Error: Unable to fetch Linear issues" >&2
+ return 1
+ }
+ echo "$issues"
+}
+
+# Synchronize a single issue with Taskwarrior
+sync_to_taskwarrior() {
+ issue_line="$1"
+ issue_id=$(echo "$issue_line" | jq -r '.id')
+ issue_description=$(echo "$issue_line" | jq -r '.description')
+ issue_repo_name=$(echo "$issue_line" | jq -r '.repository' | awk -F/ '{print $NF}')
+ issue_url=$(echo "$issue_line" | jq -r '.html_url')
+
+ log "Processing Issue ID: $issue_id, Description: $issue_description"
+
+ task_uuid=$(get_task_id_by_description "$issue_description")
+
+ if [ -z "$task_uuid" ]; then
+ log "Creating new task for issue: $issue_description"
+ task_uuid=$(create_task "$issue_description" "+$issue_repo_name" "project:$issue_repo_name")
+
+ if [ -n "$task_uuid" ]; then
+ annotate_task "$task_uuid" "$issue_url"
+ log "Task created and annotated for: $issue_description"
+ else
+ log "Error: Failed to create task for: $issue_description" >&2
+ fi
+ else
+ log "Task already exists for: $issue_description (UUID: $task_uuid)"
+ fi
+}
+
+# Mark a GitHub issue as completed in Taskwarrior
+sync_github_issue() {
+ task_description="$1"
+ task_uuid=$(get_task_id_by_description "$task_description")
+
+ if [ -n "$task_uuid" ]; then
+ mark_task_completed "$task_uuid"
+ log "Task marked as completed: $task_description (UUID: $task_uuid)"
+ else
+ log "Task UUID not found for: $task_description" >&2
+ fi
+}
+
+# Compare existing Taskwarrior tasks with current issues and mark as completed if not present
+compare_and_display_tasks_not_in_issues() {
+ existing_task_descriptions="$1"
+ issues_descriptions="$2"
+
+ log "Starting comparison of Taskwarrior tasks and current issues."
+
+ existing_task_descriptions_array=$(echo "$existing_task_descriptions" | tr '\n' ' ')
+ issues_descriptions_array=$(echo "$issues_descriptions" | tr '\n' ' ')
+
+ for task_description in $existing_task_descriptions_array; do
+ trimmed_task_description=$(trim_whitespace "$task_description")
+ issue_exists=false
+
+ for issue_description in $issues_descriptions_array; do
+ trimmed_issue_description=$(trim_whitespace "$issue_description")
+ if [ "$(echo "$trimmed_task_description" | tr '[:upper:]' '[:lower:]')" = "$(echo "$trimmed_issue_description" | tr '[:upper:]' '[:lower:]')" ]; then
+ issue_exists=true
+ break
+ fi
+ done
+
+ if [ "$issue_exists" = false ]; then
+ log "No matching issue found for task: $trimmed_task_description. Marking as completed."
+ sync_github_issue "$trimmed_task_description"
+ fi
+ done
+
+ log "Comparison of Taskwarrior tasks and issues completed."
+}
+
+# Retrieve existing Taskwarrior task descriptions with +github or +linear tags and pending status
+get_existing_task_descriptions() {
+ task +github or +linear status:pending export | jq -r '.[] | .description'
+}
+
+# Log retrieved issues
+log_issues() {
+ issue_type="$1"
+ issues="$2"
+ log "Retrieved $issue_type issues: $(echo "$issues" | jq '.')"
+}
+
+# Synchronize all issues to Taskwarrior
+sync_issues_to_taskwarrior() {
+ issues="$1"
+ echo "$issues" | jq -c '.' | while IFS= read -r line; do
+ sync_to_taskwarrior "$line"
+ done
+}
+
+# Main function to orchestrate the synchronization
+main() {
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
+ echo "Not a git repository."
+ exit 1
+ fi
+ validate_env_vars
+
+ github_issues=$(get_github_issues)
+ linear_issues=$(get_linear_issues)
+
+ if [ -z "$github_issues" ] && [ -z "$linear_issues" ]; then
+ log "No issues retrieved from GitHub or Linear. Exiting."
+ exit 0
+ fi
+
+ if [ -n "$github_issues" ]; then
+ log_issues "GitHub" "$github_issues"
+ sync_issues_to_taskwarrior "$github_issues"
+ fi
+
+ if [ -n "$linear_issues" ]; then
+ log_issues "Linear" "$linear_issues"
+ sync_issues_to_taskwarrior "$linear_issues"
+ fi
+
+ existing_task_descriptions=$(get_existing_task_descriptions)
+
+ compare_and_display_tasks_not_in_issues "$existing_task_descriptions" "$(
+ echo "$github_issues" | jq -r '.description'
+ echo "$linear_issues" | jq -r '.description'
+ )"
+}
+
+main
diff --git a/ar/.local/bin/task/taskwarrior-tui/increase-priority b/ar/.local/bin/task/taskwarrior-tui/increase-priority
new file mode 100755
index 0000000..26a3d53
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/increase-priority
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# The set -e option instructs the shell to immediately exit if any command has a non-zero exit status
+# The set -u option treats unset variables (except for $* and $@) as an error
+set -eu
+
+echo "Increase duration of task $*"
+
+# Fetch the duration value using taskwarrior and jq
+dur=$(task "$@" export | jq -r '.[0].duration')
+
+# Check if the duration has hours
+case "$dur" in
+*H*)
+ # Extract both hour and minute parts
+ hour=$(echo "$dur" | awk -F 'T' '{split($2, a, "H"); print a[1]}')
+ minute=$(echo "$dur" | awk -F 'H' '{split($2, a, "M"); print a[1]}')
+
+ # Add 30 to the minutes
+ new_minute=$((minute + 30))
+
+ # Check if minutes are 60 or more
+ if [ "$new_minute" -ge 60 ]; then
+ # Add 1 to the hour
+ new_hour=$((hour + 1))
+
+ # Calculate new minutes
+ new_minute=$((new_minute - 60))
+ else
+ # Keep the original hour
+ new_hour=$hour
+ fi
+
+ # Create the new duration string
+ new_dur="PT${new_hour}H${new_minute}M"
+ ;;
+*)
+ # If duration is already 30M
+ if [ "$dur" = "PT30M" ]; then
+ # Make it 1H
+ new_dur="PT1H"
+ else
+ # Extract the minute part from the duration string
+ minute=$(echo "$dur" | awk -F 'T' '{split($2, a, "M"); print a[1]}')
+
+ # Add 30 to the minutes
+ new_minute=$((minute + 30))
+
+ # Create the new duration string
+ new_dur="PT${new_minute}M"
+ fi
+ ;;
+esac
+
+# Print the new duration
+echo "Original duration: $dur"
+echo "New duration: $new_dur"
+
+# Modify the task duration using taskwarrior
+task rc.bulk=0 rc.confirmation=off rc.dependency.confirmation=off rc.recurrence.confirmation=off "$@" modify duration="$new_dur"
diff --git a/ar/.local/bin/task/taskwarrior-tui/lib-task-interop b/ar/.local/bin/task/taskwarrior-tui/lib-task-interop
new file mode 100755
index 0000000..04e60ac
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/lib-task-interop
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+# Interop functions for Taskwarrior
+
+# Create a new task in Taskwarrior with a given description and optional additional attributes.
+# Properly handle special characters in the description and other arguments.
+create_task() {
+ description="$1"
+ shift # Now $@ contains the rest of the arguments
+
+ # Use a variable to hold arguments to prevent word splitting
+ task_args=""
+ for arg in "$@"; do
+ task_args="$task_args $arg"
+ done
+
+ # Use -- to indicate the end of options, and pass the description safely
+ output=$(task add $task_args -- "$description")
+
+ # Extract the UUID from the output using a reliable method
+ task_uuid=$(echo "$output" | grep -o 'Created task [a-z0-9\-]*' | awk '{print $3}')
+
+ echo "$task_uuid"
+}
+
+# Get task UUID from description with specific tags (+github or +linear)
+get_task_id_by_description() {
+ description="$1"
+ # Use task export with tags +github or +linear and status:pending to find the task by description
+ task +github or +linear status:pending export | jq -r --arg desc "$description" '.[] | select(.description == $desc) | .uuid'
+}
+
+# Annotate an existing task
+annotate_task() {
+ task_uuid="$1"
+ annotation="$2"
+ task "$task_uuid" annotate -- "$annotation"
+}
+
+# Mark a task as completed
+mark_task_completed() {
+ task_uuid="$1"
+ echo "Attempting to mark task $task_uuid as completed..."
+ task "$task_uuid" done || {
+ echo "Failed to mark task $task_uuid as completed" >&2
+ exit 1
+ }
+}
+
+# Mark a task as pending
+mark_task_pending() {
+ task_uuid="$1"
+ task "$task_uuid" modify status:pending
+}
+
+# Get task labels (tags)
+get_task_labels() {
+ task_uuid="$1"
+ echo "Getting labels for task $task_uuid"
+ task _get "$task_uuid".tags
+}
+
+# Add a label (tag) to a task
+add_task_label() {
+ task_uuid="$1"
+ label="$2"
+ task "$task_uuid" modify +"$label"
+}
+
+# Remove a label (tag) from a task
+remove_task_label() {
+ task_uuid="$1"
+ label="$2"
+ task "$task_uuid" modify -"$label"
+}
+
+# Set or change a task's project
+change_task_project() {
+ task_uuid="$1"
+ project_name="$2"
+ task "$task_uuid" modify project:"$project_name"
+}
diff --git a/ar/.local/bin/task/taskwarrior-tui/task-switch-context b/ar/.local/bin/task/taskwarrior-tui/task-switch-context
new file mode 100755
index 0000000..0b40141
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/task-switch-context
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+switch_context() {
+ task context "$1"
+ echo "Switched to $1 context."
+}
+
+# Get the current task context
+current_context=$(task _get rc.context)
+
+# Check if the current context is "work"
+if [ "$current_context" = "work" ]; then
+ switch_context home
+ # Set the marker file to indicate we should switch back to work next time
+elif [ "$current_context" = "home" ]; then
+ # If we're in home and the marker file exists, it means we want to switch back to work
+ switch_context work
+ # Remove the marker file after switching back to avoid repeated switches
+elif [ -z "$current_context" ]; then
+ switch_context home
+else
+ echo "Current context is: $current_context. No action taken."
+fi
diff --git a/ar/.local/bin/task/taskwarrior-tui/taskopen-annotation b/ar/.local/bin/task/taskwarrior-tui/taskopen-annotation
new file mode 100755
index 0000000..3b292e8
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/taskopen-annotation
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -eu
+
+if [ -z "$2" ]; then
+ echo "Second argument is unset" >>/tmp/taskopen_debug.log
+else
+ echo "Second argument is set to '$2'" >>/tmp/taskopen_debug.log
+fi
+
+taskopen "$1"
diff --git a/ar/.local/bin/task/taskwarrior-tui/taskopen-line b/ar/.local/bin/task/taskwarrior-tui/taskopen-line
new file mode 100755
index 0000000..832b30e
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/taskopen-line
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# Capture the current session name
+current_session=$(tmux display-message -p '#S')
+
+# Sleep for a bit to allow tui to load
+sleep 0.1
+
+# Extract the line number and file path from the input string
+LINE_NUMBER=$(echo "$1" | awk -F ':' '{print $2}')
+FILE_PATH=$(echo "$1" | awk -F ':' '{print $3}')
+
+# Resolve the file path if it's a symlink
+if [ -L "$FILE_PATH" ]; then
+ FILE_PATH=$(readlink -f "$FILE_PATH")
+fi
+
+# Use all arguments beyond the first one as the TASK_DESCRIPTION
+shift
+TASK_DESCRIPTION="$*"
+
+# If a task description is provided, search for the line number containing that description
+if [ -n "$TASK_DESCRIPTION" ]; then
+ NEW_LINE_NUMBER=$(grep -n -F "$TASK_DESCRIPTION" "$FILE_PATH" | awk -F ':' '{print $1}' | head -n 1)
+ if [ -n "$NEW_LINE_NUMBER" ]; then
+ LINE_NUMBER=$NEW_LINE_NUMBER
+ fi
+fi
+
+# Capture the file name from the file path without the extension
+FILE_NAME=$(basename "$FILE_PATH" | awk -F '.' '{print $1}')
+DIR_NAME=$(dirname "$FILE_PATH")
+
+# Check if directory exists
+if [ ! -d "$DIR_NAME" ]; then
+ exit 1
+fi
+
+# Create a new tmux session which opens the file with neovim at the specific line number
+cd "$DIR_NAME" && tmux new-session -d -s "$FILE_NAME" "nvim +$LINE_NUMBER $FILE_PATH"
+
+# Attach to the new session
+tmux switch-client -t "$FILE_NAME"
+
+# Wait for the session to be closed, either by the user or some other way
+while tmux has-session -t "$FILE_NAME" 2>/dev/null; do
+ sleep 1
+done
+
+# Switch back to the original session
+tmux switch-client -t "$current_session"
diff --git a/ar/.local/bin/task/taskwarrior-tui/tasks-sync b/ar/.local/bin/task/taskwarrior-tui/tasks-sync
new file mode 100755
index 0000000..06334c4
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/tasks-sync
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if [ "$(task _get tw.syncneeded)" -eq 1 ]; then
+ if ! task sync 2>/dev/null; then
+ task config sync.encryption_secret "$(pass show server/task_secret)"
+ task config sync.gcp.bucket "task.thesiah.xyz"
+ task config sync.gcp.credential_path "${XDG_DATA_HOME:-${HOME}/.local/share}/task/task-sync-server.json"
+
+ task sync
+ fi
+fi
diff --git a/ar/.local/bin/task/taskwarrior-tui/toggle-review-label b/ar/.local/bin/task/taskwarrior-tui/toggle-review-label
new file mode 100755
index 0000000..9133c30
--- /dev/null
+++ b/ar/.local/bin/task/taskwarrior-tui/toggle-review-label
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+uuid="$1"
+# Use task _tags command to get current tags
+current_tags=$(task _tags "$uuid")
+
+if echo "$current_tags" | grep -q "review"; then
+ # Remove review tag if present
+ task rc.bulk=0 rc.confirmation=off "$uuid" modify -review manual_priority: -next
+else
+ # Add review tag if not present
+ task rc.bulk=0 rc.confirmation=off "$uuid" modify +review
+fi
diff --git a/ar/.local/bin/td-toggle b/ar/.local/bin/td-toggle
new file mode 100755
index 0000000..5dc4d49
--- /dev/null
+++ b/ar/.local/bin/td-toggle
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# If transmission-daemon is running, will ask to kill, else will ask to start.
+
+if pidof transmission-daemon >/dev/null; then
+ [ "$(printf "Yes\\nNo" | dmenu -i -p "Turn off transmission-daemon?")" = "Yes" ] && killall transmission-daemon && notify-send "❌ transmission-daemon disabled."
+else
+ ifinstalled transmission-cli || exit
+ [ "$(printf "Yes\\nNo" | dmenu -i -p "Turn on transmission daemon?")" = "Yes" ] && transmission-daemon && notify-send "🚄 transmission-daemon enabled."
+fi
+sleep 3 && pkill -RTMIN+22 "${STATUSBAR:-dwmblocks}"
diff --git a/ar/.local/bin/texclear b/ar/.local/bin/texclear
new file mode 100755
index 0000000..58f4439
--- /dev/null
+++ b/ar/.local/bin/texclear
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Clears the build files of a LaTeX/XeLaTeX build.
+# I have vim run this file whenever I exit a .tex file.
+
+[ "${1##*.}" = "tex" ] && {
+ find "$(dirname "${1}")" -regex '.*\(_minted.*\|.*\.\(4tc\|xref\|tmp\|pyc\|pyg\|pyo\|fls\|vrb\|fdb_latexmk\|bak\|swp\|aux\|log\|synctex\(busy\)\|lof\|lot\|maf\|idx\|mtc\|mtc0\|nav\|out\|snm\|toc\|bcf\|run\.xml\|synctex\.gz\|blg\|bbl\)\)' -delete
+} || printf "Provide a .tex file.\n"
diff --git a/ar/.local/bin/timer b/ar/.local/bin/timer
new file mode 100755
index 0000000..fd4bf5e
--- /dev/null
+++ b/ar/.local/bin/timer
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+if [ -z "$1" ]; then
+ echo "Usage: ${0##*/} <mins> [message]"
+ exit 1
+fi
+
+mins=$1
+message=$2
+
+nohup timer_ "$mins" "$message" >/dev/null 2>&1 &
+
+echo "timer started for $mins min"
+logger -t timer "timer started for $mins min, message: '$message'"
diff --git a/ar/.local/bin/timer_ b/ar/.local/bin/timer_
new file mode 100755
index 0000000..665e61f
--- /dev/null
+++ b/ar/.local/bin/timer_
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -eu
+
+mins=$1
+message=${2:-Time out!}
+
+# Calculate the sleep time in seconds
+sleep_time=$(expr "$mins" \* 60)
+sleep "$sleep_time"
+
+# Send a notification using a POSIX-compliant command
+# 'notify-send' is not POSIX, but we'll keep it since it's a common tool
+# For complete POSIX compliance, replace this with another method if needed
+notify-send -t 0 "${message}" "Your timer of $mins min is over" -u normal
diff --git a/ar/.local/bin/timezones b/ar/.local/bin/timezones
new file mode 100755
index 0000000..206f8da
--- /dev/null
+++ b/ar/.local/bin/timezones
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -e
+
+[ "$1" != "" ] && time=$1 || time=''
+
+printDate() {
+ [ "$time" != "" ] && message="$(TZ=$2 date +'%a, %d %b %H:%M' -d "$time $(date +%Z)")" || message="$(TZ=$2 date +'%a, %d %b %H:%M')"
+ notify-send "$1" "$message"
+}
+
+printDate "Los Angeles" America/Los_Angeles
+printDate "New York" America/New_York
+printDate "London" Europe/London
+printDate "Berlin" Europe/Berlin
+printDate "Moscow" Europe/Moscow
+printDate "Bangkok" Asia/Bangkok
+printDate "Hong Kong" Asia/Hong_Kong
+printDate "Macau" Asia/Macau
+printDate "Seoul" Asia/Seoul
+printDate "Tokyo" Asia/Tokyo
diff --git a/ar/.local/bin/tmuxcreate b/ar/.local/bin/tmuxcreate
new file mode 100755
index 0000000..5fb5ef3
--- /dev/null
+++ b/ar/.local/bin/tmuxcreate
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+create_new_session() {
+ session_name=$1
+ session_path=${2:-"$PWD"} # Default to current directory if no path is provided
+ [ -z "$session_name" ] && { printf "New session name: " && read -r session_name; }
+ if tmux has-session -t "$session_name" 2>/dev/null; then
+ tmux switch-client -t "$session_name"
+ else
+ if [ -n "$TMUX" ]; then
+ tmux new-session -d -s "$session_name" -c "$session_path"
+ tmux switch-client -t "$session_name"
+ else
+ tmux new -s "$session_name" -c "$session_path"
+ fi
+ fi
+}
+
+if [ $# -gt 0 ]; then
+ if [ -d "$1" ]; then
+ create_new_session "$(basename "$1")" "$1"
+ else
+ create_new_session "$1"
+ fi
+else
+ # Capture the output of tmux ls
+ sessions=$(tmux ls 2>/dev/null)
+ if [ -z "$sessions" ]; then
+ create_new_session
+ else
+ session=$( (
+ echo "$sessions"
+ echo "[new session]"
+ ) | fzf-tmux --reverse | cut -d: -f1)
+ [ -z "$session" ] && exit
+ if [ "$session" = "[new session]" ]; then
+ create_new_session
+ else
+ tmux attach -t "$session"
+ fi
+ fi
+fi
diff --git a/ar/.local/bin/tmuxopen b/ar/.local/bin/tmuxopen
new file mode 100755
index 0000000..ff1c275
--- /dev/null
+++ b/ar/.local/bin/tmuxopen
@@ -0,0 +1,193 @@
+#!/bin/sh
+
+usage() {
+ echo "Search for files and open them in Neovim within tmux panes."
+ echo ""
+ echo "Usage: tmuxopen [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " -h, --help : Show this help message"
+ echo " -d, --debug : Enable debug output"
+ echo ""
+ echo "Controls:"
+ echo " Tab Select files"
+ echo " Ctrl+f Search filenames"
+ echo " Ctrl+g Search file contents"
+ echo " Ctrl+d Search directories"
+ echo ""
+ echo "Environment Variables:"
+ echo " NVIM_SEARCH_REGISTRY Set to the search query, allowing Neovim to highlight matches"
+ echo ""
+ echo "Example:"
+ echo " tmuxopen # Run the normal search and open"
+ echo " tmuxopen --debug # Run with debug output"
+}
+
+debug() {
+ [ "$DEBUG" -eq 1 ] && echo "DEBUG: $*" >&2
+}
+
+get_fzf_output() {
+ RG_BIND="ctrl-g:reload:rg --line-number --no-heading --color=always --smart-case --glob '!**/.git/**' --glob '!node_modules/**' '' 2>/dev/null || true"
+ FILE_BIND="ctrl-f:reload:rg --files --glob '!**/.git/**' --glob '!node_modules/**' 2>/dev/null || true"
+ if command -v fd >/dev/null 2>&1; then
+ DIR_BIND="ctrl-d:change-prompt(directory> )+reload(cd \"$HOME\" && echo \"$HOME\"; fd --type d --hidden --absolute-path --color never --exclude .git --exclude node_modules)"
+ else
+ DIR_BIND="ctrl-d:change-prompt(directory> )+reload(cd \"$HOME\" && find \"$HOME\" -type d -name node_modules -prune -o -name .git -prune -o -type d -print)"
+ fi
+
+ rg --line-number --no-heading --color=always --smart-case --glob '!**/.git/**' --glob '!LICENSE' '' 2>/dev/null |
+ fzf-tmux \
+ --ansi --multi --delimiter : \
+ --reverse \
+ --print-query \
+ --preview 'bat --style=numbers --color=always --highlight-line {2} {1} 2>/dev/null || echo "Preview not available"' \
+ --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
+ --bind "$FILE_BIND" \
+ --bind "$RG_BIND" \
+ --bind "$DIR_BIND" \
+ --bind 'ctrl-c:abort' \
+ --header "^f filenames, ^g contents, ^d directories"
+}
+
+set_nvim_search_variable() {
+ raw_output="$1"
+ query=$(echo "$raw_output" | head -n1)
+ export NVIM_SEARCH_REGISTRY="$query"
+}
+
+open_files_in_nvim() {
+ pane="$1"
+ shift
+ file_indices="$*"
+ nvim_cmd="nvim"
+ for index in $file_indices; do
+ file=$(echo "$files" | awk -v idx="$index" '{print $idx}')
+ line=$(echo "$lines" | awk -v idx="$index" '{print $idx}')
+ nvim_cmd="$nvim_cmd +$line $file"
+ done
+ nvim_cmd="$nvim_cmd -c 'let @/=\"$NVIM_SEARCH_REGISTRY\"'"
+ debug "Running command in pane $pane: $nvim_cmd"
+ tmux send-keys -t "$pane" "$nvim_cmd" C-m
+}
+
+# Main logic
+DEBUG=0
+
+# Parse command line arguments
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -d | --debug)
+ DEBUG=1
+ shift
+ ;;
+ -h | --help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1" >&2
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+for cmd in rg fzf bat tmux nvim; do
+ if ! command -v "$cmd" >/dev/null 2>&1; then
+ echo "Error: $cmd not found" >&2
+ exit 1
+ fi
+done
+
+if [ -z "$TMUX" ]; then
+ echo "Error: Not in a tmux session" >&2
+ exit 1
+fi
+
+raw_output=$(get_fzf_output)
+debug "Raw fzf output:"
+debug "$raw_output"
+set_nvim_search_variable "$raw_output"
+
+# Split the newline-delimited output into an array, skipping the first line (query)
+selections=$(echo "$raw_output" | sed 1d)
+
+debug "Number of selections: $(echo "$selections" | wc -l)"
+debug "Selections:"
+debug "$selections"
+if [ -z "$selections" ]; then
+ debug "No selections made"
+ exit 0
+fi
+
+files=""
+lines=""
+count=0
+
+# Use a here document to avoid subshell issues
+while IFS= read -r selection; do
+ file=$(echo "$selection" | awk -F: '{print $1}')
+ line=$(echo "$selection" | awk -F: '{print $2}')
+ debug "Processing selection: $selection"
+ debug "File: $file, Line: $line"
+ if [ -f "$file" ]; then
+ files="$files $file"
+ lines="$lines $line"
+ count=$((count + 1))
+ else
+ debug "File not found: $file"
+ fi
+done <<EOF
+$selections
+EOF
+
+debug "Number of valid files: $count"
+debug "Valid files:"
+debug "$files"
+if [ "$count" -eq 0 ]; then
+ debug "No valid files selected"
+ exit 0
+fi
+
+if [ "$count" -eq 1 ]; then
+ debug "Opening single file"
+ open_files_in_nvim "$(tmux display-message -p '#P')" 1
+else
+ debug "Opening multiple files"
+ window_name="TheSiahxyz-$(date +%s)"
+ tmux new-window -n "$window_name"
+ case "$count" in
+ 2)
+ debug "Opening 2 files side-by-side"
+ tmux split-window -t "$window_name" -h -p 50
+ open_files_in_nvim "$window_name.1" 1
+ open_files_in_nvim "$window_name.2" 2
+ tmux select-pane -t "$window_name.1"
+ ;;
+ 3)
+ debug "Opening 3 files"
+ tmux split-window -t "$window_name" -h -p 50
+ tmux split-window -t "$window_name.2" -v -p 50
+ open_files_in_nvim "$window_name.1" 1
+ open_files_in_nvim "$window_name.2" 2
+ open_files_in_nvim "$window_name.3" 3
+ ;;
+ *)
+ debug "Opening 4 or more files"
+ tmux split-window -t "$window_name" -h -p 50
+ tmux split-window -t "$window_name.1" -v -p 50
+ tmux split-window -t "$window_name.3" -v -p 50
+ open_files_in_nvim "$window_name.1" 1
+ open_files_in_nvim "$window_name.2" 2
+ open_files_in_nvim "$window_name.3" 3
+ remaining_indices=""
+ for i in $(seq 4 "$count"); do
+ remaining_indices="$remaining_indices $i"
+ done
+ open_files_in_nvim "$window_name.4" "$remaining_indices"
+ ;;
+ esac
+fi
+
+debug "Script completed"
diff --git a/ar/.local/bin/tmuxtogglebar b/ar/.local/bin/tmuxtogglebar
new file mode 100755
index 0000000..2ae045e
--- /dev/null
+++ b/ar/.local/bin/tmuxtogglebar
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+case "$(tmux show-option -gqv status)" in
+on) tmux set-option -g status off ;;
+off) tmux set-option -g status on ;;
+esac
diff --git a/ar/.local/bin/tmuxtoggleterm b/ar/.local/bin/tmuxtoggleterm
new file mode 100755
index 0000000..f21f833
--- /dev/null
+++ b/ar/.local/bin/tmuxtoggleterm
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+export TMUX_PANE_DIRECTION="bottom"
+
+if [ "$TMUX_PANE_DIRECTION" = "bottom" ]; then
+ tmux select-pane -U
+elif [ "$TMUX_PANE_DIRECTION" = "right" ]; then
+ tmux select-pane -L
+fi
+
+tmux resize-pane -Z
diff --git a/ar/.local/bin/tordone b/ar/.local/bin/tordone
new file mode 100755
index 0000000..4e097a0
--- /dev/null
+++ b/ar/.local/bin/tordone
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+completed_torrents=$(transmission-remote -n "$USER" -l | grep 100% | awk '{print $1}')
+if [ -n "$completed_torrents" ]; then
+ for torrent_id in $completed_torrents; do
+ transmission-remote -n "$USER" -t "$torrent_id" -r
+ done
+ pkill -RTMIN+22 "${STATUSBAR:-dwmblocks}" && notify-send "✅ Transmission-daemon" "Torrent(s) $TR_TORRENT_NAME has completed downloading. Deleting torrent files."
+ [ -z "$(transmission-remote -n "$USER" -l | grep -v "Sum:")" ] && killall transmission-daemon && notify-send "❌ Transmission-daemon disabled."
+fi
diff --git a/ar/.local/bin/torwrap b/ar/.local/bin/torwrap
new file mode 100755
index 0000000..3d32cc5
--- /dev/null
+++ b/ar/.local/bin/torwrap
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+ifinstalled stig transmission-cli || exit 1
+
+! pidof transmission-daemon >/dev/null && transmission-daemon && notify-send "Starting torrent daemon..."
+
+$TERMINAL -e stig
+pkill -RTMIN+22 "${STATUSBAR:-dwmblocks}"
diff --git a/ar/.local/bin/transadd b/ar/.local/bin/transadd
new file mode 100755
index 0000000..ffd8ded
--- /dev/null
+++ b/ar/.local/bin/transadd
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Mimeapp script for adding torrent to transmission-daemon, but will also start the daemon first if not running.
+
+# transmission-daemon sometimes fails to take remote requests in its first moments, hence the sleep.
+
+pidof transmission-daemon >/dev/null || (transmission-daemon && notify-send "💡 Starting transmission daemon..." && sleep 3 && pkill -RTMIN+22 "${STATUSBAR:-dwmblocks}")
+
+directory="$HOME/Torrents"
+
+[ "$1" = "-l" ] && {
+ URL=$(xclip -selection clipboard -o)
+ case "$URL" in
+ http://* | https://* | magnet:?*)
+ transmission-remote -a "$URL" && notify-send "🔽 Torrent added."
+ exit 0
+ ;;
+ *)
+ added_torrents=$(transmission-remote -l | grep -vE '^ID|Sum' | awk '{print $NF}' | sed 's/\.torrent$//')
+ filtered_files=$(ls "$directory"/*.torrent 2>/dev/null | sed "s|^$directory/||" | sed 's/\.torrent$//' | grep -vF "$added_torrents")
+ [ -n "$filtered_files" ] && {
+ choice=$(echo "$filtered_files" | dmenu -i -l 20 -p "Select Torrent:")
+ [ -n "$choice" ] && transmission-remote -a "$directory/$choice.torrent" && notify-send "🔽 Torrent added."
+ } || notify-send "🤷 No new torrent found."
+ ;;
+ esac
+} || {
+ transmission-remote -a "$@" && notify-send "🔽 Torrent added." "$TR_TORRENT_NAME"
+}
diff --git a/ar/.local/bin/tutorialvids b/ar/.local/bin/tutorialvids
new file mode 100755
index 0000000..1bac3df
--- /dev/null
+++ b/ar/.local/bin/tutorialvids
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# This gives the user a list of videos they can select and watch without a
+# browser. If you want to check a tutorial video, it makes it easy. I'll
+# add/remove videos from this list as I go on.
+
+vidlist="
+dwm (window manager) https://videos.lukesmith.xyz/videos/watch/f6b78db7-b368-4647-bc64-28c08fff1988
+dwmblocks (status bar) https://videos.lukesmith.xyz/w/mmxHMbqZZEr5FManB57Yy1
+pacman (installing/managing programs) https://videos.lukesmith.xyz/videos/watch/8e7cadb9-0fed-47ce-a2a8-6635fa48614b
+sxiv/nsxiv (image viewer) https://videos.lukesmith.xyz/videos/watch/ad4c8d85-90c3-4f3d-a1f3-89129e64a3c2
+st (terminal) https://videos.lukesmith.xyz/videos/watch/efddd39d-bac5-4599-b572-177beb4ce6e8
+i3 (old window manager) https://videos.lukesmith.xyz/videos/watch/b861525c-7ada-40ee-a2bb-b5e1ffe0f48b
+neomutt (email) https://videos.lukesmith.xyz/videos/watch/83122e83-52d9-4278-ae1a-7d1beeb50c8e
+ncmpcpp (music player) https://videos.lukesmith.xyz/videos/watch/b5ac6f0d-a220-4433-88e3-e98fc791dc0a
+newsboat (RSS reader) https://videos.lukesmith.xyz/videos/watch/bd2c3fff-40fa-47ea-aa98-5b1ec0c903b6
+lf (file manager) https://videos.lukesmith.xyz/w/rKeHsF5ZHDNDbR1buUKB1c
+zathura (pdf viewer) https://videos.lukesmith.xyz/videos/watch/c780f75a-11f6-48a9-a191-d079ebc36ea4
+gpg keys https://videos.lukesmith.xyz/videos/watch/040f5530-4830-4583-9ddc-2080b421531b
+calcurse (calendar) https://videos.lukesmith.xyz/videos/watch/4b937e8b-7654-46e3-8d01-79392ec5b3d1
+urlview https://videos.lukesmith.xyz/videos/watch/31a4918f-633b-4bd6-b08e-956ac75d0324
+colorschemes with pywal https://videos.lukesmith.xyz/videos/watch/1b476003-61b2-4609-ac4b-820c3d128643
+vi mode in shell https://videos.lukesmith.xyz/videos/watch/228aa50c-836f-456f-9f0d-a45157fe4313
+pass (password manager) https://videos.lukesmith.xyz/videos/watch/432fc942-5e28-4682-9beb-f5cb237a1dd6
+"
+echo "$vidlist" | grep -P "^$(echo "$vidlist" | grep "https:" | sed 's/\t.*//g' | dmenu -i -p "Learn about what? (ESC to cancel)" -l 20 | awk '{print $1}')\s" | sed 's/.*\t//' | xargs -r mpv
diff --git a/ar/.local/bin/unewsboat b/ar/.local/bin/unewsboat
new file mode 100755
index 0000000..547fae2
--- /dev/null
+++ b/ar/.local/bin/unewsboat
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+export FIFO_UEBERZUG="/tmp/vifm-ueberzug-${PPID}"
+
+function cleanup {
+ rm "$FIFO_UEBERZUG" 2>/dev/null
+ pkill -P $$ 2>/dev/null
+}
+
+rm "$FIFO_UEBERZUG" 2>/dev/null
+mkfifo "$FIFO_UEBERZUG"
+trap cleanup EXIT
+tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser bash &
+
+newsboat "$@"
+cleanup
diff --git a/ar/.local/bin/unix b/ar/.local/bin/unix
new file mode 100755
index 0000000..a9fb96e
--- /dev/null
+++ b/ar/.local/bin/unix
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+#original artwork by http://www.sanderfocus.nl/#/portfolio/tech-heroes
+#converted to shell by #nixers @ irc.unix.chat
+
+cat << 'eof'
+ ,_ ,_==▄▂
+ , ▂▃▄▄▅▅▅▂▅¾. / /
+ ▄▆<´ "»▓▓▓%\ / / / /
+ ,▅7" ´>▓▓▓% / / > / >/%
+ ▐¶▓ ,»▓▓¾´ /> %/%// / /
+ ▓▃▅▅▅▃,,▄▅▅▅Æ\// ///>// />/ /
+ V║«¼.;→ ║<«.,`=// />//%/% / /
+ //╠<´ -²,)(▓~"-╝/¾/ %/>/ />
+ / / / ▐% -./▄▃▄▅▐, /7//;//% / /
+ / ////`▌▐ %zWv xX▓▇▌//&;% / /
+ / / / %//%/¾½´▌▃▄▄▄▄▃▃▐¶\/& /
+ </ /</%//`▓!%▓%╣[38;5;255;╣WY<Y)y&/`\
+ / / %/%//</%//\i7; ╠N>)VY>7; \_ UNIX IS VERY SIMPLE IT JUST NEEDS A
+ / /</ //<///<_/%\▓ V%W%£)XY _/%‾\_, GENIUS TO UNDERSTAND ITS SIMPLICITY
+ / / //%/_,=--^/%/%%\¾%¶%%} /%%%%%%;\,
+ %/< /_/ %%%%%;X%%\%%;, _/%%%;, \
+ / / %%%%%%;, \%%l%%;// _/%;, dmr
+ / %%%;, <;\-=-/ /
+ ;, l
+eof
diff --git a/ar/.local/bin/unmounter b/ar/.local/bin/unmounter
new file mode 100755
index 0000000..1e413bf
--- /dev/null
+++ b/ar/.local/bin/unmounter
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Unmount USB drives or Android phones. Replaces the older `dmenuumount`. Fewer
+# prompt and also de-decrypts LUKS drives that are unmounted.
+
+set -e
+
+mounteddroids="$(grep simple-mtpfs /etc/mtab | awk '{print "📱" $2}')"
+lsblkoutput="$(lsblk -nrpo "name,type,size,mountpoint")"
+mounteddrives="$(echo "$lsblkoutput" | awk '($2=="part"||$2="crypt")&&$4!~/\/boot|\/home$|SWAP/&&length($4)>1{printf "💾%s (%s)\n",$4,$3}')"
+mountedcifs="$(grep cifs /etc/mtab | awk '{print "🪟" $2}')"
+
+allunmountable="$(echo "$mounteddroids
+$mounteddrives
+$mountedcifs" | sed "/^$/d;s/ *$//")"
+test -n "$allunmountable"
+
+chosen="$(echo "$allunmountable" | dmenu -i -p "Unmount which drive?")"
+chosen="${chosen%% *}"
+test -n "$chosen"
+
+if grep -q "/${chosen#*/}" /etc/mtab | grep -q "cifs" /etc/mtab; then
+ sudo -A umount "/${chosen#*/}" && sudo -A rm -r "/${chosen#*/}"
+ notify-send "⏏️ SMB Drive unmounted." "/${chosen#*/} has been unmounted."
+ exit 0
+fi
+
+[ "${chosen#*/}" = "${chosen}" ] && [ "${chosen##*/}" = "$(sudo lsblk -no "label" "$(df "/${chosen#*/}" | tail -n 1 | awk '{print $1}')")" ] && rmcheck=true || rmcheck=false
+mountpath="$(sudo lsblk -no "mountpoints" "$(df "/${chosen#*/}" | tail -n 1 | awk '{print $1}')")"
+sudo -A umount -l "/${chosen#*/}"
+[ "/media/$USER/${chosen##*/}" = "$mountpath" ] && [ "$rmcheck" ] && {
+ [ -e "/${chosen#*/}" ] && [ -z "$(ls -A "/${chosen#*/}")" ] && (rm -rf "/${chosen#*/}" >/dev/null 2>&1 || sudo rm -rf "/${chosen#*/}") || {
+ rmdiryn=$(printf "No\\nYes" | dmenu -i -p "Do you want to delete /${chosen#*/}?")
+ [ "$rmdiryn" = "Yes" ] && (rm -rf "/${chosen#*/}" >/dev/null 2>&1 || sudo -A rm -r "/${chosen#*/}")
+ }
+}
+notify-send "⏏️ Device unmounted." "$chosen has been unmounted."
+
+# Close the chosen drive if decrypted.
+cryptid="$(echo "$lsblkoutput" | grep "/${chosen#*/}$")"
+cryptid="${cryptid%% *}"
+test -b /dev/mapper/"${cryptid##*/}"
+sudo -A cryptsetup close "$cryptid"
+notify-send "🔒 Device dencryption closed." "Drive is now securely locked again."
diff --git a/ar/.local/bin/vipy b/ar/.local/bin/vipy
new file mode 100755
index 0000000..ac30f30
--- /dev/null
+++ b/ar/.local/bin/vipy
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# Create a new notebook JSON structure
+NOTEBOOK='{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython"
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}'
+
+[ -z "$1" ] && echo "Enter a file name!" && exit
+
+# Write the JSON to a new .ipynb file
+echo "$NOTEBOOK" >"$1.ipynb"
+
+# Check if the file was created successfully
+[ -f "$1.ipynb" ] && echo "$1.ipynb created successfully." || echo "Failed to create a Jupyter Notebook."
diff --git a/ar/.local/bin/wallset b/ar/.local/bin/wallset
new file mode 100755
index 0000000..e2414cb
--- /dev/null
+++ b/ar/.local/bin/wallset
@@ -0,0 +1,295 @@
+#!/bin/bash
+# vim: noai:ts=4:sw=4:expandtab
+#
+# wallset: This program customizes images and videos for wallpaper
+# website: http://terminalroot.com.br/
+# author: Marcos Oliveira
+# dependencies: feh, ffmpeg, imagemagick(convert), xrandr, sed, bash, xdg-utils
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+shopt -s extglob
+
+if [[ $(echo $LANG | cut -c 1-2) == 'pt' ]]; then
+ _lang=("uso" "opções" "Altera o Wallpaper para o número informado. Use imagens com 3 dígitos, exemplo: 014,003,099" "Adiciona imagens" "Use antes do parâmetro -a,--add quando quiser adicionar já alterar" "Finaliza o loop" "Cria um loop de imagem com o tempo informado em segundos" "Abre a última imagem adicionada" "Informa quantas imagens há" "Mostra a atual imagem" "Adiciona um vídeo como Wallpaper" "Lista os vídeos que já foram utilizados" "Utiliza o vídeo pelo número listado em --list-videos" "Remove a última imagem adicionada" "Exibe essa ajuda" "Exibe a versão desse programa" "Exemplos" "Adiciona uma imagem" "Adiciona e já define como Wallpaper" "Altera para imagem com esse número" "Adiciona o vídeo" "Usa o vídeo já utilizado e listado" "Significa que precisa usar um número" "Somente vídeos nos formato .mp4" "Somente será exibido 10 segundos iniciais do vídeo" "Use imagens com 3 dígitos, exemplo: 011" "Não há nenhuma imagem." "Preparando o vídeo" "Use números indicados por" "Use números de 1 até" "Tempo inválido" "Tipo inválido" "Use números de 001 até" "Formato inválido." "Somente formato MP4" "Número inválido. Use" "Informe o vídeo" "Não há imagens." "Não há vídeos.")
+else
+ _lang=("usage" "options" "Change the Wallpaper to the number entered. Use images with 3 digits, example: 014,003,099" "Add images" "Use before the -a, --add parameter when you want to add already change" "Ends the loop" "Creates an image loop with the time reported in seconds" "Opens the last image added" "Informs how many images there are" "Shows the current image" "Add a video as Wallpaper" "Lists the videos that have already been used" "Use the video by the number listed in --list-videos" "Remove the last image added" "Display this help" "Display the version of this program" "Examples" "Add an image" "Add and set it as Wallpaper" "Change to image with this number" "Add the video" "Use the video already used and listed" "Means that you need to use a number" "Only videos in .mp4 format" "Only the first 10 seconds of the video will be displayed" "Use 3-digit images, example: 011" "There is no image." "Preparing the video..." "Use numbers indicated by" "Use numbers numbers from 1 to" "Invalid time" "Invalid type" "Use numbers from 001 to" "Invalid format." "MP4 format only" "Invalid number. Use" "Report Video" "No images." "No videos.")
+fi
+
+_usage() {
+ cat <<EOF
+${_lang[0]}: ${0##*/} [${_lang[1]}]
+
+ Options:
+ -u,--use [N] ${_lang[2]}
+ -a,--add img.jpg ... ${_lang[3]}
+ -S,--set ${_lang[4]}
+ -q,--quit ${_lang[5]}
+ -t,--time [N] ${_lang[6]}
+ -d,--display ${_lang[7]}
+ -c,--count ${_lang[8]}
+ -s,--show ${_lang[9]}
+ -V,--video [video] ${_lang[10]}
+ -L,--list-videos ${_lang[11]}
+ -I,--set-video [N] ${_lang[12]}
+ -r,--remove ${_lang[13]}
+ -h,--help ${_lang[14]}
+ -v,--version ${_lang[15]}
+
+ ${_lang[16]}:
+ ${_lang[17]} → '${0##*/} -a img.jpg'
+ ${_lang[18]} → '${0##*/} --set --add img.jpg'
+ ${_lang[19]} → '${0##*/} -u 001'
+ ${_lang[20]} → '${0##*/} -V video.mp4'
+ ${_lang[21]} → '${0##*/} -I 3'
+
+* [N] ${_lang[22]}
+** ${_lang[23]}
+*** ${_lang[24]}
+**** ${_lang[25]}
+EOF
+ exit
+}
+
+_dir_img="${HOME}/.local/share/wallpapers"
+_dir_vid="${_dir_img}/video"
+pid="$$"
+
+[[ ! -d "${_dir_img}" ]] && mkdir -p "${_dir_img}"
+[[ ! -d "${_dir_vid}" ]] && mkdir -p "${_dir_vid}"
+ctrl_c() {
+ exit 127
+}
+
+_get_resolution_screen() {
+ xrandr | grep '*' | awk '{print $1}' | cut -dx -f1
+}
+
+_last_wall() {
+ ls ${_dir_img}/*.jpg 2>/dev/null >/dev/null
+ if [[ "$?" != "0" ]]; then
+ printf "%s" "0"
+ else
+ _n=$(ls -1 ${_dir_img}/*.jpg | tail -n 1 | tr -d 'a-z\.\-\/')
+ printf "%s" "${_n}"
+ fi
+}
+
+_new_number() {
+ _n=$(echo $(_last_wall) + 1 | bc)
+ [[ $(expr length ${_n}) == 1 ]] && _n="00${_n}" || _n="0${_n}"
+ printf "%s" "${_n}"
+
+}
+
+_resize_img() {
+ _n=$(_new_number)
+ _w=$(_get_resolution_screen)
+ convert -resize "${_w}" "${1}" "${_dir_img}/wallpaper-${_n}.jpg"
+ if [[ "${add}" == 1 ]]; then
+ feh --no-fehbg --bg-scale "${_dir_img}/wallpaper-${_n}.jpg"
+ [[ $(which gsettings >&- 2>&-) ]] &&
+ gsettings set org.gnome.desktop.background picture-uri file:///"${_dir_img}/wallpaper-${_n}.jpg"
+ fi
+}
+
+_count() {
+ ls ${_dir_img}/*.jpg 2>/dev/null | wc -l
+}
+
+_current() {
+ xdg-open "${_dir_img}/wallpaper-$(_last_wall).jpg"
+}
+
+_loop() {
+ if [[ $(_count) < 1 ]]; then
+ printf "%s\n" "${_lang[26]}"
+ exit 1
+ fi
+ i=1
+ while :; do
+ [[ "${i}" -ge "$(_count)" ]] && i=1
+ if [[ $(expr length $i) == 1 ]]; then
+ feh --no-fehbg --bg-scale "${_dir_img}/wallpaper-00${i}.jpg"
+ else
+ feh --no-fehbg --bg-scale "${_dir_img}/wallpaper-0${i}.jpg"
+ fi
+ sleep $1
+ let i=i+1
+
+ done &
+
+}
+
+_quit_loop() {
+ #kill -9 $$
+ killall -q bash 2>/dev/null >/dev/null
+}
+
+_set_img() {
+ ls "${_dir_img}/wallpaper-${1}.jpg" >/dev/null 2>/dev/null
+ if [[ "$?" == 0 ]]; then
+ feh --no-fehbg --bg-scale "${_dir_img}/wallpaper-$1.jpg"
+ if [[ $(which gsettings >&- 2>&-) && "$XDG_CURRENT_DESKTOP" == "GNOME" ]]; then
+ gsettings set org.gnome.desktop.background picture-uri file:///"${_dir_img}/wallpaper-$1.jpg"
+ fi
+ else
+ _t=$(_count)
+ [[ "${_t}" -lt 100 ]] && _t="0${_t}"
+ echo "$1 - ${_lang[35]}: "$(ls ${_dir_img} | sed 's/wallpaper-//;s/\.jpg/ /g')
+ fi
+
+}
+
+_show() {
+ mpv --really-quiet "${XDG_DATA_HOME:-${HOME}/.local/share}/wallpapers/video/bg.mp4"
+}
+
+_video() {
+ a=1
+ for i in $(ls -d $_dir_vid/*/ 2>/dev/null); do
+ if [[ -d "$_dir_vid/$a" ]]; then
+ let a=a+1
+ fi
+ done
+
+ mkdir -p "$_dir_vid/$a"
+
+ echo -e "${_lang[27]}...\r"
+ ffmpeg -y -ss 00:00 -i ${1} -q:v 1 -t 10 "${_dir_vid}/${a}/filename%05d.jpg" >${_dir_vid}/${a}/video.info 2>&1
+ t=$(ls ${_dir_vid}/${a}/* | wc -l)
+ let t=t-1
+ echo ${1} >${_dir_vid}/${a}/video.info
+
+ i=1
+ # ctrl_c
+ while :; do
+ feh --no-fehbg --bg-scale $(ls -1 ${_dir_vid}/${a}/* | sed -n "${i}p")
+ sleep 0.01
+ let i=i+1
+ [[ "${i}" -gt "${t}" ]] && i=1
+ done &
+}
+
+_list_vid() {
+ ls -d ${_dir_vid}/* 2>/dev/null >/dev/null
+ if [[ "$?" != "0" ]]; then
+ echo ${_lang[38]}
+ exit 1
+ fi
+ for i in $(ls -1 ${_dir_vid}/*/video.info); do
+ echo $i | awk 'BEGIN { FS = "/" } ; { print $6 }'
+ cat $i
+ done | paste - - | sed 's/\t/ → /g'
+}
+
+_set_video() {
+ ls -d ${_dir_vid}/* 2>/dev/null >/dev/null
+ if [[ "$?" != "0" ]]; then
+ echo ${_lang[38]}
+ exit 1
+ fi
+ if [[ $(echo "$1" | sed -n -r '/^[0-9]+$/p') == "" ]]; then
+ echo "${_lang[28]} '${0##*/} --list-videos'."
+ else
+ if [[ "$1" -gt "$(ls -d1 ${_dir_vid}/* | wc -l)" ]]; then
+ echo "${_lang[29]} $(ls -d1 ${_dir_vid}/* | wc -l)"
+ exit 1
+ else
+ t=$(ls ${_dir_vid}/${1}/* | wc -l)
+ let t=t-1
+ i=1
+ # ctrl_c
+ while :; do
+ feh --no-fehbg --bg-scale $(ls -1 ${_dir_vid}/${1}/* | sed -n "${i}p")
+ sleep 0.01
+ let i=i+1
+ [[ "${i}" -gt "${t}" ]] && i=1
+ done &
+ fi
+ fi
+}
+
+while [[ "$1" ]]; do
+ case "${1}" in
+ -h | --help) _usage ;;
+ -v | --version) echo "${0##*/} 0.0.1-beta" ;;
+ -s | --show) _show && exit ;;
+ -c | --count) _count ;;
+ -d | --display) _current ;;
+ -t | --time)
+ shift
+ if [[ $(echo "$1" | sed -n -r '/^[0-9]+$/p') == "" ]]; then
+ echo "${_lang[30]}"
+ else
+ _loop "$1"
+ fi
+ ;;
+ -q | --quit) _quit_loop ;;
+ -S | --set) export add=1 ;;
+ -a | --add)
+ shift
+ while [[ -f "${1}" ]]; do
+ if [[ "$(file ${1} | awk '{print $2}')" == @(PNG|JPEG) ]]; then
+ _resize_img $1
+ else
+ echo "${_lang[31]}: $1"
+ fi
+ shift
+ done
+ ;;
+ -u | --use)
+ shift
+
+ ls ${_dir_img}/*.jpg 2>/dev/null >/dev/null
+ if [[ "$?" != "0" ]]; then
+ echo "${_lang[37]}"
+ exit 1
+ fi
+
+ if [[ $(echo "$1" | sed -n -r '/^[0-9]+$/p') == "" ]]; then
+ echo "${_lang[32]} $(_last_wall)"
+ else
+ if [[ "$1" > "$(_last_wall)" ]]; then
+ echo "${_lang[32]} $(_last_wall)"
+ else
+ _set_img "$1"
+ fi
+ fi
+ ;;
+ -r | --remove-last) rm "${_dir_img}/wallpaper-$(_last_wall).jpg" ;;
+ --video | -V)
+ shift
+ [[ -z "$1" ]] && {
+ echo "${_lang[36]}"
+ exit 1
+ }
+ [[ $(file -b --mime-type ${1} | cut -d"/" -f1) != 'video' ]] && {
+ echo "${_lang[33]}"
+ exit 1
+ }
+ [[ $(file -b --mime-type ${1} | cut -d"/" -f2) != @(mp4|x-m4v) ]] && {
+ echo "${_lang[34]}"
+ exit 1
+ }
+ _video "${1}"
+ ;;
+ -L | --list-videos) _list_vid ;;
+ -I | --set-video)
+ shift
+ _set_video "${1}"
+ ;;
+ esac
+ shift
+done
diff --git a/ar/.local/bin/weath b/ar/.local/bin/weath
new file mode 100755
index 0000000..d013f6f
--- /dev/null
+++ b/ar/.local/bin/weath
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Get the weather on the terminal. You can pass an alternative location as a parameter,
+# and/or use the 'cp' option to copy the forecast as plaintext to the clipboard.
+
+weatherreport="${XDG_CACHE_HOME:-${HOME}/.cache}/weatherreport"
+
+formats() {
+ [ "$MANPAGER" = "less -s" ] && pager=true || pager=false
+ [ "$pager" = "false" ] && {
+ export MANPAGER='less -s'
+ export LESS="R"
+ export LESS_TERMCAP_mb="$(printf '%b' '')"
+ export LESS_TERMCAP_md="$(printf '%b' '')"
+ export LESS_TERMCAP_me="$(printf '%b' '')"
+ export LESS_TERMCAP_so="$(printf '%b' '')"
+ export LESS_TERMCAP_se="$(printf '%b' '')"
+ export LESS_TERMCAP_us="$(printf '%b' '')"
+ export LESS_TERMCAP_ue="$(printf '%b' '')"
+ export LESSOPEN="| /usr/bin/highlight -O ansi %s 2>/dev/null"
+ }
+ setsid -f "$TERMINAL" less -S "$1" >/dev/null 2>&1
+ [ "$pager" = "false" ] && {
+ export MANPAGER="sh -c 'col -bx | bat -l man -p'"
+ export MANROFFOPT="-c"
+ }
+}
+
+if [ "$1" = 'cp' ]; then
+ # shellcheck disable=SC2015
+ [ -z "$2" ] && sed 's/\x1b\[[^m]*m//g' "$weatherreport" | xclip -selection clipboard &&
+ notify-send "Weather forecast for '${LOCATION:-$(head -n 1 "$weatherreport" | cut -d' ' -f3-)}' copied to clipboard." ||
+ { data="$(curl -sfm 5 "${WTTRURL:-wttr.in}/$2?T")" &&
+ notify-send "🌞Weather forecast for '$2' copied to clipboard." &&
+ echo "$data" | xclip -selection clipboard ||
+ notify-send '🥶Failed to get weather forecast!' 'Check your internet connection and the supplied location.'; }
+else
+ [ -n "$2" ] &&
+ notify-send "⛔Invalid option '$1'! The only valid option is 'cp'." &&
+ exit 1
+
+ # shellcheck disable=SC2015
+ [ -z "$1" ] && formats "$weatherreport" ||
+ data="$(curl -sfm 5 "${WTTRURL:-wttr.in}/$1")" && echo "$data" | formats - ||
+ notify-send '❗Failed to get weather forecast!' 'Check your internet connection and the supplied location.'
+fi
diff --git a/ar/.local/bin/whereami b/ar/.local/bin/whereami
new file mode 100755
index 0000000..0531376
--- /dev/null
+++ b/ar/.local/bin/whereami
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+arch=$(uname -m)
+os_type=$(uname | tr '[:upper:]' '[:lower:]')
+
+case "${os_type},${arch}" in
+"linux,arm" | "linux,arm64" | "linux,x86_64")
+ [ -f /etc/os-release ] &&
+ grep PRETTY_NAME /etc/os-release | cut -d'=' -f2- | sed 's/"//g' | awk -F' ' '{print $1}' | tr '[:upper:]' '[:lower:]' | sed 's/\(arch\|artix\)/ar/g'
+ ;;
+"darwin,arm64")
+ echo "m1"
+ ;;
+"darwin,x86_64")
+ echo "mac"
+ ;;
+"msys,"* | "cygwin,"* | "windows,"*)
+ echo "windows"
+ ;;
+*)
+ echo "Unsupported OS"
+ exit
+ ;;
+esac
diff --git a/ar/.local/bin/xdg-terminal-exec b/ar/.local/bin/xdg-terminal-exec
new file mode 100755
index 0000000..12b18ff
--- /dev/null
+++ b/ar/.local/bin/xdg-terminal-exec
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+"$TERMINAL" -e "$@"
diff --git a/ar/.local/bin/xinputconf b/ar/.local/bin/xinputconf
new file mode 100755
index 0000000..f469666
--- /dev/null
+++ b/ar/.local/bin/xinputconf
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+device_name="$(xinput list | grep '↳' | sed 's/.*↳ //g' | fzf)"
+[ -n "$device_name" ] && device_id="$(xinput list | grep -F "↳ $device_name" | sed -n 's/.*id=\([0-9]\+\).*/\1/p')" || exit
+
+show_matrix() {
+ printf "%s\n" "$(xinput list-props "$device_id" | awk '/Coordinate Transformation Matrix/{print $0}' | sed 's/^[[:space:]]*//g')"
+}
+
+# Function to set new speed and scrolling speed values
+set_speeds() {
+ printf "Set trackpoint speed: "
+ read -r speed
+
+ printf "Set trackpoint scrolling speed: "
+ read -r scroll_speed
+
+ prop_id=$(xinput list-props "$device_name" | awk '/Coordinate Transformation Matrix/ {match($0, /\(([0-9]+)\)/, a); print a[1]}')
+
+ if [ -n "$prop_id" ]; then
+ xinput set-prop "$device_name" "$prop_id" "$speed, 0, 0, 0, $scroll_speed, 0, 0, 0, 1"
+ show_matrix
+ else
+ printf "Property ID for Coordinate Transformation Matrix not found.\n" >&2
+ return 1
+ fi
+}
+
+case "$1" in
+-s)
+ set_speeds
+ ;;
+-l | "")
+ show_matrix
+ ;;
+*)
+ echo "Invalid option. Use -s to set speeds or -l to list the matrix."
+ ;;
+esac
diff --git a/ar/.lynxrc b/ar/.lynxrc
new file mode 100644
index 0000000..315956b
--- /dev/null
+++ b/ar/.lynxrc
@@ -0,0 +1,344 @@
+# Lynx User Defaults File
+#
+# This file contains options saved from the Lynx Options Screen (normally
+# with the 'o' key). To save options with that screen, you must select the
+# checkbox:
+# Save options to disk
+#
+# You must then save the settings using the link on the line above the
+# checkbox:
+# Accept Changes
+#
+# You may also use the command-line option "-forms_options", which displays
+# the simpler Options Menu instead. Save options with that using the '>' key.
+#
+# There is normally no need to edit this file manually, since the defaults
+# here can be controlled from the Options Screen, and the next time options
+# are saved from the Options Screen this file will be completely rewritten.
+# You have been warned...
+#
+# If you are looking for the general configuration file - it is normally
+# called "lynx.cfg". It has different content and a different format.
+# It is not this file.
+
+# accept_all_cookies allows the user to tell Lynx to automatically
+# accept all cookies if desired. The default is "FALSE" which will
+# prompt for each cookie. Set accept_all_cookies to "TRUE" to accept
+# all cookies.
+accept_all_cookies=off
+
+# anonftp_password allows the user to tell Lynx to use the personal
+# email address as the password for anonymous ftp. If no value is given,
+# Lynx will use the personal email address. Set anonftp_password
+# to a different value if you choose.
+anonftp_password=
+
+# bookmark_file specifies the name and location of the default bookmark
+# file into which the user can paste links for easy access at a later
+# date.
+bookmark_file=lynx_bookmarks.html
+
+# If case_sensitive_searching is "on" then when the user invokes a search
+# using the 's' or '/' keys, the search performed will be case sensitive
+# instead of case INsensitive. The default is usually "off".
+case_sensitive_searching=off
+
+# The character_set definition controls the representation of 8 bit
+# characters for your terminal. If 8 bit characters do not show up
+# correctly on your screen you may try changing to a different 8 bit
+# set or using the 7 bit character approximations.
+# Current valid characters sets are:
+# Western (ISO-8859-1)
+# 7 bit approximations (US-ASCII)
+# Western (ISO-8859-15)
+# Western (cp850)
+# Western (windows-1252)
+# IBM PC US codepage (cp437)
+# DEC Multinational
+# Macintosh (8 bit)
+# NeXT character set
+# HP Roman8
+# Chinese
+# Japanese (EUC-JP)
+# Japanese (Shift_JIS)
+# Korean
+# Taipei (Big5)
+# Vietnamese (VISCII)
+# Transparent
+# Eastern European (ISO-8859-2)
+# Eastern European (cp852)
+# Eastern European (windows-1250)
+# Latin 3 (ISO-8859-3)
+# Latin 4 (ISO-8859-4)
+# Baltic Rim (ISO-8859-13)
+# Baltic Rim (cp775)
+# Baltic Rim (windows-1257)
+# Cyrillic (ISO-8859-5)
+# Cyrillic (cp866)
+# Cyrillic (windows-1251)
+# Cyrillic (KOI8-R)
+# Arabic (ISO-8859-6)
+# Arabic (cp864)
+# Arabic (windows-1256)
+# Celtic (ISO-8859-14)
+# Greek (ISO-8859-7)
+# Greek (cp737)
+# Greek2 (cp869)
+# Greek (windows-1253)
+# Hebrew (ISO-8859-8)
+# Hebrew (cp862)
+# Hebrew (windows-1255)
+# Turkish (ISO-8859-9)
+# Turkish (cp857)
+# North European (ISO-8859-10)
+# Latin 10 (ISO-8859-16)
+# UNICODE (UTF-8)
+# RFC 1345 w/o Intro
+# RFC 1345 Mnemonic
+# Ukrainian Cyrillic (cp866u)
+# Ukrainian Cyrillic (KOI8-U)
+# Cyrillic-Asian (PT154)
+character_set=Western (ISO-8859-1)
+
+# cookie_accept_domains and cookie_reject_domains are comma-delimited
+# lists of domains from which Lynx should automatically accept or reject
+# all cookies. If a domain is specified in both options, rejection will
+# take precedence. The accept_all_cookies parameter will override any
+# settings made here.
+cookie_accept_domains=
+
+# cookie_file specifies the file from which to read persistent cookies.
+# The default is ~/.lynx_cookies.
+cookie_file=
+
+# cookie_loose_invalid_domains, cookie_strict_invalid_domains, and
+# cookie_query_invalid_domains are comma-delimited lists of which domains
+# should be subjected to varying degrees of validity checking. If a
+# domain is set to strict checking, strict conformance to RFC2109 will
+# be applied. A domain with loose checking will be allowed to set cookies
+# with an invalid path or domain attribute. All domains will default to
+# querying the user for an invalid path or domain.
+cookie_loose_invalid_domains=
+
+cookie_query_invalid_domains=
+
+cookie_reject_domains=
+
+cookie_strict_invalid_domains=
+
+# dir_list_order specifies the directory list order under DIRED_SUPPORT
+# (if implemented). The default is "ORDER_BY_NAME"
+dir_list_order=ORDER_BY_NAME
+
+# dir_list_styles specifies the directory list style under DIRED_SUPPORT
+# (if implemented). The default is "MIXED_STYLE", which sorts both
+# files and directories together. "FILES_FIRST" lists files first and
+# "DIRECTORIES_FIRST" lists directories first.
+dir_list_style=MIXED_STYLE
+
+# If emacs_keys is to "on" then the normal EMACS movement keys:
+# ^N = down ^P = up
+# ^B = left ^F = right
+# will be enabled.
+emacs_keys=off
+
+# file_editor specifies the editor to be invoked when editing local files
+# or sending mail. If no editor is specified, then file editing is disabled
+# unless it is activated from the command line, and the built-in line editor
+# will be used for sending mail.
+file_editor=nvim
+
+# The file_sorting_method specifies which value to sort on when viewing
+# file lists such as FTP directories. The options are:
+# BY_FILENAME -- sorts on the name of the file
+# BY_TYPE -- sorts on the type of the file
+# BY_SIZE -- sorts on the size of the file
+# BY_DATE -- sorts on the date of the file
+file_sorting_method=BY_FILENAME
+
+# If keypad_mode is set to "NUMBERS_AS_ARROWS", then the numbers on
+# your keypad when the numlock is on will act as arrow keys:
+# 8 = Up Arrow
+# 4 = Left Arrow 6 = Right Arrow
+# 2 = Down Arrow
+# and the corresponding keyboard numbers will act as arrow keys,
+# regardless of whether numlock is on.
+# If keypad_mode is set to "LINKS_ARE_NUMBERED", then numbers will
+# appear next to each link and numbers are used to select links.
+# If keypad_mode is set to "LINKS_AND_FORM_FIELDS_ARE_NUMBERED", then
+# numbers will appear next to each link and visible form input field.
+# Numbers are used to select links, or to move the "current link" to a
+# form input field or button. In addition, options in popup menus are
+# indexed so that the user may type an option number to select an option in
+# a popup menu, even if the option isn't visible on the screen. Reference
+# lists and output from the list command also enumerate form inputs.
+# NOTE: Some fixed format documents may look disfigured when
+# "LINKS_ARE_NUMBERED" or "LINKS_AND_FORM_FIELDS_ARE_NUMBERED" are
+# enabled.
+keypad_mode=LINKS_ARE_NOT_NUMBERED
+
+# lineedit_mode specifies the key binding used for inputting strings in
+# prompts and forms. If lineedit_mode is set to "Default Binding" then
+# the following control characters are used for moving and deleting:
+#
+# Prev Next Enter = Accept input
+# Move char: <- -> ^G = Cancel input
+# Move word: ^P ^N ^U = Erase line
+# Delete char: ^H ^R ^A = Beginning of line
+# Delete word: ^B ^F ^E = End of line
+#
+# Current lineedit modes are:
+# Default Binding
+# Alternate Bindings
+# Bash-like Bindings
+lineedit_mode=Default Binding
+
+# The following allow you to define sub-bookmark files and descriptions.
+# The format is multi_bookmark<capital_letter>=<filename>,<description>
+# Up to 26 bookmark files (for the English capital letters) are allowed.
+# We start with "multi_bookmarkB" since 'A' is the default (see above).
+multi_bookmarkB=
+multi_bookmarkC=
+multi_bookmarkD=
+multi_bookmarkE=
+multi_bookmarkF=
+multi_bookmarkG=
+multi_bookmarkH=
+multi_bookmarkI=
+multi_bookmarkJ=
+multi_bookmarkK=
+multi_bookmarkL=
+multi_bookmarkM=
+multi_bookmarkN=
+multi_bookmarkO=
+multi_bookmarkP=
+multi_bookmarkQ=
+multi_bookmarkR=
+multi_bookmarkS=
+multi_bookmarkT=
+multi_bookmarkU=
+multi_bookmarkV=
+multi_bookmarkW=
+multi_bookmarkX=
+multi_bookmarkY=
+multi_bookmarkZ=
+
+# personal_mail_address specifies your personal mail address. The
+# address will be sent during HTTP file transfers for authorization and
+# logging purposes, and for mailed comments.
+# If you do not want this information given out, set the NO_FROM_HEADER
+# to TRUE in lynx.cfg, or use the -nofrom command line switch. You also
+# could leave this field blank, but then you won't have it included in
+# your mailed comments.
+personal_mail_address=
+
+# personal_mail_name specifies your personal name, for mail. The
+# name is sent for mailed comments. Lynx will prompt for this,
+# showing the configured value as a default when sending mail.
+# This is not necessarily the same as a name provided as part of the
+# personal_mail_address.
+# Lynx does not save your changes to that default value as a side-effect
+# of sending email. To update the default value, you must use the options
+# menu, or modify this file directly.
+personal_mail_name=
+
+# preferred_charset specifies the character set in MIME notation (e.g.,
+# ISO-8859-2, ISO-8859-5) which Lynx will indicate you prefer in requests
+# to http servers using an Accept-Charset header. The value should NOT
+# include ISO-8859-1 or US-ASCII, since those values are always assumed
+# by default. May be a comma-separated list.
+# If a file in that character set is available, the server will send it.
+# If no Accept-Charset header is present, the default is that any
+# character set is acceptable. If an Accept-Charset header is present,
+# and if the server cannot send a response which is acceptable
+# according to the Accept-Charset header, then the server SHOULD send
+# an error response, though the sending of an unacceptable response
+# is also allowed.
+preferred_charset=
+
+# preferred_language specifies the language in MIME notation (e.g., en,
+# fr, may be a comma-separated list in decreasing preference)
+# which Lynx will indicate you prefer in requests to http servers.
+# If a file in that language is available, the server will send it.
+# Otherwise, the server will send the file in its default language.
+preferred_language=en
+
+# select_popups specifies whether the OPTIONs in a SELECT block which
+# lacks a MULTIPLE attribute are presented as a vertical list of radio
+# buttons or via a popup menu. Note that if the MULTIPLE attribute is
+# present in the SELECT start tag, Lynx always will create a vertical list
+# of checkboxes for the OPTIONs. A value of "on" will set popup menus
+# as the default while a value of "off" will set use of radio boxes.
+# The default can be overridden via the -popup command line toggle.
+select_popups=on
+
+# show_color specifies how to set the color mode at startup. A value of
+# "never" will force color mode off (treat the terminal as monochrome)
+# at startup even if the terminal appears to be color capable. A value of
+# "always" will force color mode on even if the terminal appears to be
+# monochrome, if this is supported by the library used to build lynx.
+# A value of "default" will yield the behavior of assuming
+# a monochrome terminal unless color capability is inferred at startup
+# based on the terminal type, or the -color command line switch is used, or
+# the COLORTERM environment variable is set. The default behavior always is
+# used in anonymous accounts or if the "option_save" restriction is set.
+# The effect of the saved value can be overridden via
+# the -color and -nocolor command line switches.
+# The mode set at startup can be changed via the "show color" option in
+# the 'o'ptions menu. If the option settings are saved, the "on" and
+# "off" "show color" settings will be treated as "default".
+show_color=default
+
+# show_cursor specifies whether to 'hide' the cursor to the right (and
+# bottom, if possible) of the screen, or to place it to the left of the
+# current link in documents, or current option in select popup windows.
+# Positioning the cursor to the left of the current link or option is
+# helpful for speech or braille interfaces, and when the terminal is
+# one which does not distinguish the current link based on highlighting
+# or color. A value of "on" will set positioning to the left as the
+# default while a value of "off" will set 'hiding' of the cursor.
+# The default can be overridden via the -show_cursor command line toggle.
+show_cursor=on
+
+# show_dotfiles specifies that the directory listing should include
+# "hidden" (dot) files/directories. If set "on", this will be
+# honored only if enabled via userdefs.h and/or lynx.cfg, and not
+# restricted via a command line switch. If display of hidden files
+# is disabled, creation of such files via Lynx also is disabled.
+show_dotfiles=off
+
+# If sub_bookmarks is not turned "off", and multiple bookmarks have
+# been defined (see below), then all bookmark operations will first
+# prompt the user to select an active sub-bookmark file. If the default
+# Lynx bookmark_file is defined (see above), it will be used as the
+# default selection. When this option is set to "advanced", and the
+# user mode is advanced, the 'v'iew bookmark command will invoke a
+# statusline prompt instead of the menu seen in novice and intermediate
+# user modes. When this option is set to "standard", the menu will be
+# presented regardless of user mode.
+sub_bookmarks=OFF
+
+# user_mode specifies the users level of knowledge with Lynx. The
+# default is "NOVICE" which displays two extra lines of help at the
+# bottom of the screen to aid the user in learning the basic Lynx
+# commands. Set user_mode to "INTERMEDIATE" to turn off the extra info.
+# Use "ADVANCED" to see the URL of the currently selected link at the
+# bottom of the screen.
+user_mode=NOVICE
+
+# If verbose_images is "on", lynx will print the name of the image
+# source file in place of [INLINE], [LINK] or [IMAGE]
+# See also VERBOSE_IMAGES in lynx.cfg
+verbose_images=on
+
+# If vi_keys is set to "on", then the normal VI movement keys:
+# j = down k = up
+# h = left l = right
+# will be enabled. These keys are only lower case.
+# Capital 'H', 'J' and 'K will still activate help, jump shortcuts,
+# and the keymap display, respectively.
+vi_keys=on
+
+# The visited_links setting controls how Lynx organizes the information
+# in the Visited Links Page.
+visited_links=LAST_REVERSED
diff --git a/ar/.stow-local-ignore b/ar/.stow-local-ignore
new file mode 100644
index 0000000..d7913e8
--- /dev/null
+++ b/ar/.stow-local-ignore
@@ -0,0 +1,34 @@
+# Comments and blank lines are allowed.
+
+RCS
+.+,v
+
+CVS
+\.\#.+ # CVS conflict files / emacs lock files
+\.cvsignore
+
+\.svn
+_darcs
+\.hg
+
+^\.git$
+^\.gitignore$
+
+.+~ # emacs backup files
+\#.*\# # emacs autosave files
+
+^/.stow-local-ignore
+^/README.*
+^/LICENSE.*
+^/COPYING
+^/.*\.org
+
+misc
+
+asus.*
+blacklist
+nvidia.hook
+shell/shortcutrc
+shell/zshnameddirrc
+tmux/plugins
+vim/*/
diff --git a/default/.gnupg/gpg-agent.conf b/default/.gnupg/gpg-agent.conf
new file mode 100644
index 0000000..c469d06
--- /dev/null
+++ b/default/.gnupg/gpg-agent.conf
@@ -0,0 +1,3 @@
+allow-preset-passphrase
+enable-ssh-support
+max-cache-ttl 86400
diff --git a/default/.gnupg/sshcontrol b/default/.gnupg/sshcontrol
new file mode 100644
index 0000000..e1b1960
--- /dev/null
+++ b/default/.gnupg/sshcontrol
@@ -0,0 +1,12 @@
+# List of allowed ssh keys. Only keys present in this file are used
+# in the SSH protocol. The ssh-add tool may add new entries to this
+# file to enable them; you may also add them manually. Comment
+# lines, like this one, as well as empty lines are ignored. Lines do
+# have a certain length limit but this is not serious limitation as
+# the format of the entries is fixed and checked by gpg-agent. A
+# non-comment line starts with optional white spaces, followed by the
+# keygrip of the key given as 40 hex digits, optionally followed by a
+# caching TTL in seconds, and another optional field for arbitrary
+# flags. Prepend the keygrip with an '!' mark to disable it.
+
+CEA80B05ABA46C5DE584655EFD7D26E81A2DFF65
diff --git a/default/.local/share/applications/csv.desktop b/default/.local/share/applications/csv.desktop
new file mode 100644
index 0000000..bfcda9e
--- /dev/null
+++ b/default/.local/share/applications/csv.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=CSV Reader
+Exec=/usr/local/bin/st -e sc-im %f
diff --git a/default/.local/share/applications/file.desktop b/default/.local/share/applications/file.desktop
new file mode 100644
index 0000000..5df1633
--- /dev/null
+++ b/default/.local/share/applications/file.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=File Manager
+Exec=/usr/local/bin/st -e lfub %u
diff --git a/default/.local/share/applications/html.desktop b/default/.local/share/applications/html.desktop
new file mode 100644
index 0000000..c009841
--- /dev/null
+++ b/default/.local/share/applications/html.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Html Viewer
+Exec=/usr/bin/lynx %u
diff --git a/default/.local/share/applications/img.desktop b/default/.local/share/applications/img.desktop
new file mode 100644
index 0000000..03883fe
--- /dev/null
+++ b/default/.local/share/applications/img.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Image Viewer
+Exec=/usr/bin/nsxiv -ac %f
diff --git a/default/.local/share/applications/mail.desktop b/default/.local/share/applications/mail.desktop
new file mode 100644
index 0000000..d24aea2
--- /dev/null
+++ b/default/.local/share/applications/mail.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Mail
+Exec=/usr/local/bin/st -e neomutt %u
diff --git a/default/.local/share/applications/office.desktop b/default/.local/share/applications/office.desktop
new file mode 100644
index 0000000..9ce2cb2
--- /dev/null
+++ b/default/.local/share/applications/office.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Document Editor
+Exec=/usr/bin/libreoffice %f
diff --git a/default/.local/share/applications/pdf.desktop b/default/.local/share/applications/pdf.desktop
new file mode 100644
index 0000000..be960b7
--- /dev/null
+++ b/default/.local/share/applications/pdf.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=PDF Reader
+Exec=/usr/bin/zathura %u
diff --git a/default/.local/share/applications/roff.desktop b/default/.local/share/applications/roff.desktop
new file mode 100644
index 0000000..c79d14e
--- /dev/null
+++ b/default/.local/share/applications/roff.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Groff/Troff Viewer
+Exec=/usr/bin/groff -mom %u -Tpdf | zathura -
diff --git a/default/.local/share/applications/rss.desktop b/default/.local/share/applications/rss.desktop
new file mode 100644
index 0000000..883a97b
--- /dev/null
+++ b/default/.local/share/applications/rss.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=RSS Feed Addition
+Exec=/usr/bin/env rssadd %U
diff --git a/default/.local/share/applications/slide.desktop b/default/.local/share/applications/slide.desktop
new file mode 100644
index 0000000..31f2ab3
--- /dev/null
+++ b/default/.local/share/applications/slide.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Slides Viewer
+Exec=/usr/local/bin/st -e slides %f
diff --git a/default/.local/share/applications/text.desktop b/default/.local/share/applications/text.desktop
new file mode 100644
index 0000000..41e8f79
--- /dev/null
+++ b/default/.local/share/applications/text.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Text Editor
+Exec=/usr/local/bin/st -e nvim %u
diff --git a/default/.local/share/applications/torrent.desktop b/default/.local/share/applications/torrent.desktop
new file mode 100644
index 0000000..f6d28d9
--- /dev/null
+++ b/default/.local/share/applications/torrent.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Torrent
+Exec=/usr/bin/env transadd %U
diff --git a/default/.local/share/applications/video.desktop b/default/.local/share/applications/video.desktop
new file mode 100644
index 0000000..2814690
--- /dev/null
+++ b/default/.local/share/applications/video.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=Video Viewer
+Exec=/usr/bin/mpv -quiet %f
diff --git a/default/.local/share/thesiah/chars/emoji b/default/.local/share/thesiah/chars/emoji
new file mode 100644
index 0000000..485b03c
--- /dev/null
+++ b/default/.local/share/thesiah/chars/emoji
@@ -0,0 +1,1667 @@
+😀 grinning face
+😃 grinning face with big eyes
+😄 grinning face with smiling eyes
+😁 beaming face with smiling eyes
+😆 grinning squinting face
+😅 grinning face with sweat
+🤣 rolling on the floor laughing
+😂 face with tears of joy
+🙂 slightly smiling face
+🙃 upside-down face
+🫠 melting face
+😉 winking face
+😊 smiling face with smiling eyes
+😇 smiling face with halo
+🥰 smiling face with hearts
+😍 smiling face with heart-eyes
+🤩 star-struck
+😘 face blowing a kiss
+😗 kissing face
+☺️ smiling face
+😚 kissing face with closed eyes
+😙 kissing face with smiling eyes
+🥲 smiling face with tear
+😋 face savoring food
+😛 face with tongue
+😜 winking face with tongue
+🤪 zany face
+😝 squinting face with tongue
+🤑 money-mouth face
+🤗 smiling face with open hands
+🤭 face with hand over mouth
+🫢 face with open eyes and hand over mouth
+🫣 face with peeking eye
+🤫 shushing face
+🤔 thinking face
+🫡 saluting face
+🤐 zipper-mouth face
+🤨 face with raised eyebrow
+😐 neutral face
+😑 expressionless face
+😶 face without mouth
+🫥 dotted line face
+😏 smirking face
+😒 unamused face
+🙄 face with rolling eyes
+😬 grimacing face
+🤥 lying face
+🫨 shaking face
+😌 relieved face
+😔 pensive face
+😪 sleepy face
+🤤 drooling face
+😴 sleeping face
+🫩 face with bags under eyes
+😷 face with medical mask
+🤒 face with thermometer
+🤕 face with head-bandage
+🤢 nauseated face
+🤮 face vomiting
+🤧 sneezing face
+🥵 hot face
+🥶 cold face
+🥴 woozy face
+😵 face with crossed-out eyes
+🤯 exploding head
+🤠 cowboy hat face
+🥳 partying face
+🥸 disguised face
+😎 smiling face with sunglasses
+🤓 nerd face
+🧐 face with monocle
+😕 confused face
+🫤 face with diagonal mouth
+😟 worried face
+🙁 slightly frowning face
+☹️ frowning face
+😮 face with open mouth
+😯 hushed face
+😲 astonished face
+😳 flushed face
+🥺 pleading face
+🥹 face holding back tears
+😦 frowning face with open mouth
+😧 anguished face
+😨 fearful face
+😰 anxious face with sweat
+😥 sad but relieved face
+😢 crying face
+😭 loudly crying face
+😱 face screaming in fear
+😖 confounded face
+😣 persevering face
+😞 disappointed face
+😓 downcast face with sweat
+😩 weary face
+😫 tired face
+🥱 yawning face
+😤 face with steam from nose
+😡 enraged face
+😠 angry face
+🤬 face with symbols on mouth
+😈 smiling face with horns
+👿 angry face with horns
+💀 skull
+☠️ skull and crossbones
+💩 pile of poo
+🤡 clown face
+👹 ogre
+👺 goblin
+👻 ghost
+👽 alien
+👾 alien monster
+🤖 robot
+😺 grinning cat
+😸 grinning cat with smiling eyes
+😹 cat with tears of joy
+😻 smiling cat with heart-eyes
+😼 cat with wry smile
+😽 kissing cat
+🙀 weary cat
+😿 crying cat
+😾 pouting cat
+🙈 see-no-evil monkey
+🙉 hear-no-evil monkey
+🙊 speak-no-evil monkey
+💌 love letter
+💘 heart with arrow
+💝 heart with ribbon
+💖 sparkling heart
+💗 growing heart
+💓 beating heart
+💞 revolving hearts
+💕 two hearts
+💟 heart decoration
+❣️ heart exclamation
+💔 broken heart
+❤️ red heart
+🩷 pink heart
+🧡 orange heart
+💛 yellow heart
+💚 green heart
+💙 blue heart
+🩵 light blue heart
+💜 purple heart
+🤎 brown heart
+🖤 black heart
+🩶 grey heart
+🤍 white heart
+💋 kiss mark
+💯 hundred points
+💢 anger symbol
+💥 collision
+💫 dizzy
+💦 sweat droplets
+💨 dashing away
+🕳️ hole
+💬 speech balloon
+🗨️ left speech bubble
+🗯️ right anger bubble
+💭 thought balloon
+💤 ZZZ
+👋 waving hand
+🤚 raised back of hand
+🖐️ hand with fingers splayed
+✋ raised hand
+🖖 vulcan salute
+🫱 rightwards hand
+🫲 leftwards hand
+🫳 palm down hand
+🫴 palm up hand
+🫷 leftwards pushing hand
+🫸 rightwards pushing hand
+👌 OK hand
+🤌 pinched fingers
+🤏 pinching hand
+✌️ victory hand
+🤞 crossed fingers
+🫰 hand with index finger and thumb crossed
+🤟 love-you gesture
+🤘 sign of the horns
+🤙 call me hand
+👈 backhand index pointing left
+👉 backhand index pointing right
+👆 backhand index pointing up
+🖕 middle finger
+👇 backhand index pointing down
+☝️ index pointing up
+🫵 index pointing at the viewer
+👍 thumbs up
+👎 thumbs down
+✊ raised fist
+👊 oncoming fist
+🤛 left-facing fist
+🤜 right-facing fist
+👏 clapping hands
+🙌 raising hands
+🫶 heart hands
+👐 open hands
+🤲 palms up together
+🤝 handshake
+🙏 folded hands
+✍️ writing hand
+💅 nail polish
+🤳 selfie
+💪 flexed biceps
+🦾 mechanical arm
+🦿 mechanical leg
+🦵 leg
+🦶 foot
+👂 ear
+🦻 ear with hearing aid
+👃 nose
+🧠 brain
+🫀 anatomical heart
+🫁 lungs
+🦷 tooth
+🦴 bone
+👀 eyes
+👁️ eye
+👅 tongue
+👄 mouth
+🫦 biting lip
+👶 baby
+🧒 child
+👦 boy
+👧 girl
+🧑 person
+👱 person: blond hair
+👨 man
+🧔 person: beard
+👩 woman
+🧓 older person
+👴 old man
+👵 old woman
+🙍 person frowning
+🙎 person pouting
+🙅 person gesturing NO
+🙆 person gesturing OK
+💁 person tipping hand
+🙋 person raising hand
+🧏 deaf person
+🙇 person bowing
+🤦 person facepalming
+🤷 person shrugging
+👮 police officer
+🕵️ detective
+💂 guard
+🥷 ninja
+👷 construction worker
+🫅 person with crown
+🤴 prince
+👸 princess
+👳 person wearing turban
+👲 person with skullcap
+🧕 woman with headscarf
+🤵 person in tuxedo
+👰 person with veil
+🤰 pregnant woman
+🫃 pregnant man
+🫄 pregnant person
+🤱 breast-feeding
+👼 baby angel
+🎅 Santa Claus
+🤶 Mrs. Claus
+🦸 superhero
+🦹 supervillain
+🧙 mage
+🧚 fairy
+🧛 vampire
+🧜 merperson
+🧝 elf
+🧞 genie
+🧟 zombie
+🧌 troll
+💆 person getting massage
+💇 person getting haircut
+🚶 person walking
+🧍 person standing
+🧎 person kneeling
+🏃 person running
+💃 woman dancing
+🕺 man dancing
+🕴️ person in suit levitating
+👯 people with bunny ears
+🧖 person in steamy room
+🧗 person climbing
+🤺 person fencing
+🏇 horse racing
+⛷️ skier
+🏂 snowboarder
+🏌️ person golfing
+🏄 person surfing
+🚣 person rowing boat
+🏊 person swimming
+⛹️ person bouncing ball
+🏋️ person lifting weights
+🚴 person biking
+🚵 person mountain biking
+🤸 person cartwheeling
+🤼 people wrestling
+🤽 person playing water polo
+🤾 person playing handball
+🤹 person juggling
+🧘 person in lotus position
+🛀 person taking bath
+🛌 person in bed
+👭 women holding hands
+👫 woman and man holding hands
+👬 men holding hands
+💏 kiss
+💑 couple with heart
+🗣️ speaking head
+👤 bust in silhouette
+👥 busts in silhouette
+🫂 people hugging
+👪 family
+👣 footprints
+🫆 fingerprint
+🏻 light skin tone
+🏼 medium-light skin tone
+🏽 medium skin tone
+🏾 medium-dark skin tone
+🏿 dark skin tone
+🦰 red hair
+🦱 curly hair
+🦳 white hair
+🦲 bald
+🐵 monkey face
+🐒 monkey
+🦍 gorilla
+🦧 orangutan
+🐶 dog face
+🐕 dog
+🦮 guide dog
+🐩 poodle
+🐺 wolf
+🦊 fox
+🦝 raccoon
+🐱 cat face
+🐈 cat
+🦁 lion
+🐯 tiger face
+🐅 tiger
+🐆 leopard
+🐴 horse face
+🫎 moose
+🫏 donkey
+🐎 horse
+🦄 unicorn
+🦓 zebra
+🦌 deer
+🦬 bison
+🐮 cow face
+🐂 ox
+🐃 water buffalo
+🐄 cow
+🐷 pig face
+🐖 pig
+🐗 boar
+🐽 pig nose
+🐏 ram
+🐑 ewe
+🐐 goat
+🐪 camel
+🐫 two-hump camel
+🦙 llama
+🦒 giraffe
+🐘 elephant
+🦣 mammoth
+🦏 rhinoceros
+🦛 hippopotamus
+🐭 mouse face
+🐁 mouse
+🐀 rat
+🐹 hamster
+🐰 rabbit face
+🐇 rabbit
+🐿️ chipmunk
+🦫 beaver
+🦔 hedgehog
+🦇 bat
+🐻 bear
+🐨 koala
+🐼 panda
+🦥 sloth
+🦦 otter
+🦨 skunk
+🦘 kangaroo
+🦡 badger
+🐾 paw prints
+🦃 turkey
+🐔 chicken
+🐓 rooster
+🐣 hatching chick
+🐤 baby chick
+🐥 front-facing baby chick
+🐦 bird
+🐧 penguin
+🕊️ dove
+🦅 eagle
+🦆 duck
+🦢 swan
+🦉 owl
+🦤 dodo
+🪶 feather
+🦩 flamingo
+🦚 peacock
+🦜 parrot
+🪽 wing
+🪿 goose
+🐸 frog
+🐊 crocodile
+🐢 turtle
+🦎 lizard
+🐍 snake
+🐲 dragon face
+🐉 dragon
+🦕 sauropod
+🦖 T-Rex
+🐳 spouting whale
+🐋 whale
+🐬 dolphin
+🦭 seal
+🐟 fish
+🐠 tropical fish
+🐡 blowfish
+🦈 shark
+🐙 octopus
+🐚 spiral shell
+🪸 coral
+🪼 jellyfish
+🦀 crab
+🦞 lobster
+🦐 shrimp
+🦑 squid
+🦪 oyster
+🐌 snail
+🦋 butterfly
+🐛 bug
+🐜 ant
+🐝 honeybee
+🪲 beetle
+🐞 lady beetle
+🦗 cricket
+🪳 cockroach
+🕷️ spider
+🕸️ spider web
+🦂 scorpion
+🦟 mosquito
+🪰 fly
+🪱 worm
+🦠 microbe
+💐 bouquet
+🌸 cherry blossom
+💮 white flower
+🪷 lotus
+🏵️ rosette
+🌹 rose
+🥀 wilted flower
+🌺 hibiscus
+🌻 sunflower
+🌼 blossom
+🌷 tulip
+🪻 hyacinth
+🌱 seedling
+🪴 potted plant
+🌲 evergreen tree
+🌳 deciduous tree
+🌴 palm tree
+🌵 cactus
+🌾 sheaf of rice
+🌿 herb
+☘️ shamrock
+🍀 four leaf clover
+🍁 maple leaf
+🍂 fallen leaf
+🍃 leaf fluttering in wind
+🪹 empty nest
+🪺 nest with eggs
+🍄 mushroom
+🪾 leafless tree
+🍇 grapes
+🍈 melon
+🍉 watermelon
+🍊 tangerine
+🍋 lemon
+🍌 banana
+🍍 pineapple
+🥭 mango
+🍎 red apple
+🍏 green apple
+🍐 pear
+🍑 peach
+🍒 cherries
+🍓 strawberry
+🫐 blueberries
+🥝 kiwi fruit
+🍅 tomato
+🫒 olive
+🥥 coconut
+🥑 avocado
+🍆 eggplant
+🥔 potato
+🥕 carrot
+🌽 ear of corn
+🌶️ hot pepper
+🫑 bell pepper
+🥒 cucumber
+🥬 leafy green
+🥦 broccoli
+🧄 garlic
+🧅 onion
+🥜 peanuts
+🫘 beans
+🌰 chestnut
+🫚 ginger root
+🫛 pea pod
+🫜 root vegetable
+🍞 bread
+🥐 croissant
+🥖 baguette bread
+🫓 flatbread
+🥨 pretzel
+🥯 bagel
+🥞 pancakes
+🧇 waffle
+🧀 cheese wedge
+🍖 meat on bone
+🍗 poultry leg
+🥩 cut of meat
+🥓 bacon
+🍔 hamburger
+🍟 french fries
+🍕 pizza
+🌭 hot dog
+🥪 sandwich
+🌮 taco
+🌯 burrito
+🫔 tamale
+🥙 stuffed flatbread
+🧆 falafel
+🥚 egg
+🍳 cooking
+🥘 shallow pan of food
+🍲 pot of food
+🫕 fondue
+🥣 bowl with spoon
+🥗 green salad
+🍿 popcorn
+🧈 butter
+🧂 salt
+🥫 canned food
+🍱 bento box
+🍘 rice cracker
+🍙 rice ball
+🍚 cooked rice
+🍛 curry rice
+🍜 steaming bowl
+🍝 spaghetti
+🍠 roasted sweet potato
+🍢 oden
+🍣 sushi
+🍤 fried shrimp
+🍥 fish cake with swirl
+🥮 moon cake
+🍡 dango
+🥟 dumpling
+🥠 fortune cookie
+🥡 takeout box
+🍦 soft ice cream
+🍧 shaved ice
+🍨 ice cream
+🍩 doughnut
+🍪 cookie
+🎂 birthday cake
+🍰 shortcake
+🧁 cupcake
+🥧 pie
+🍫 chocolate bar
+🍬 candy
+🍭 lollipop
+🍮 custard
+🍯 honey pot
+🍼 baby bottle
+🥛 glass of milk
+☕ hot beverage
+🫖 teapot
+🍵 teacup without handle
+🍶 sake
+🍾 bottle with popping cork
+🍷 wine glass
+🍸 cocktail glass
+🍹 tropical drink
+🍺 beer mug
+🍻 clinking beer mugs
+🥂 clinking glasses
+🥃 tumbler glass
+🫗 pouring liquid
+🥤 cup with straw
+🧋 bubble tea
+🧃 beverage box
+🧉 mate
+🧊 ice
+🥢 chopsticks
+🍽️ fork and knife with plate
+🍴 fork and knife
+🥄 spoon
+🔪 kitchen knife
+🫙 jar
+🏺 amphora
+🌍 globe showing Europe-Africa
+🌎 globe showing Americas
+🌏 globe showing Asia-Australia
+🌐 globe with meridians
+🗺️ world map
+🗾 map of Japan
+🧭 compass
+🏔️ snow-capped mountain
+⛰️ mountain
+🌋 volcano
+🗻 mount fuji
+🏕️ camping
+🏖️ beach with umbrella
+🏜️ desert
+🏝️ desert island
+🏞️ national park
+🏟️ stadium
+🏛️ classical building
+🏗️ building construction
+🧱 brick
+🪨 rock
+🪵 wood
+🛖 hut
+🏘️ houses
+🏚️ derelict house
+🏠 house
+🏡 house with garden
+🏢 office building
+🏣 Japanese post office
+🏤 post office
+🏥 hospital
+🏦 bank
+🏨 hotel
+🏩 love hotel
+🏪 convenience store
+🏫 school
+🏬 department store
+🏭 factory
+🏯 Japanese castle
+🏰 castle
+💒 wedding
+🗼 Tokyo tower
+🗽 Statue of Liberty
+⛪ church
+🕌 mosque
+🛕 hindu temple
+🕍 synagogue
+⛩️ shinto shrine
+🕋 kaaba
+⛲ fountain
+⛺ tent
+🌁 foggy
+🌃 night with stars
+🏙️ cityscape
+🌄 sunrise over mountains
+🌅 sunrise
+🌆 cityscape at dusk
+🌇 sunset
+🌉 bridge at night
+♨️ hot springs
+🎠 carousel horse
+🛝 playground slide
+🎡 ferris wheel
+🎢 roller coaster
+💈 barber pole
+🎪 circus tent
+🚂 locomotive
+🚃 railway car
+🚄 high-speed train
+🚅 bullet train
+🚆 train
+🚇 metro
+🚈 light rail
+🚉 station
+🚊 tram
+🚝 monorail
+🚞 mountain railway
+🚋 tram car
+🚌 bus
+🚍 oncoming bus
+🚎 trolleybus
+🚐 minibus
+🚑 ambulance
+🚒 fire engine
+🚓 police car
+🚔 oncoming police car
+🚕 taxi
+🚖 oncoming taxi
+🚗 automobile
+🚘 oncoming automobile
+🚙 sport utility vehicle
+🛻 pickup truck
+🚚 delivery truck
+🚛 articulated lorry
+🚜 tractor
+🏎️ racing car
+🏍️ motorcycle
+🛵 motor scooter
+🦽 manual wheelchair
+🦼 motorized wheelchair
+🛺 auto rickshaw
+🚲 bicycle
+🛴 kick scooter
+🛹 skateboard
+🛼 roller skate
+🚏 bus stop
+🛣️ motorway
+🛤️ railway track
+🛢️ oil drum
+⛽ fuel pump
+🛞 wheel
+🚨 police car light
+🚥 horizontal traffic light
+🚦 vertical traffic light
+🛑 stop sign
+🚧 construction
+⚓ anchor
+🛟 ring buoy
+⛵ sailboat
+🛶 canoe
+🚤 speedboat
+🛳️ passenger ship
+⛴️ ferry
+🛥️ motor boat
+🚢 ship
+✈️ airplane
+🛩️ small airplane
+🛫 airplane departure
+🛬 airplane arrival
+🪂 parachute
+💺 seat
+🚁 helicopter
+🚟 suspension railway
+🚠 mountain cableway
+🚡 aerial tramway
+🛰️ satellite
+🚀 rocket
+🛸 flying saucer
+🛎️ bellhop bell
+🧳 luggage
+⌛ hourglass done
+⏳ hourglass not done
+⌚ watch
+⏰ alarm clock
+⏱️ stopwatch
+⏲️ timer clock
+🕰️ mantelpiece clock
+🕛 twelve o’clock
+🕧 twelve-thirty
+🕐 one o’clock
+🕜 one-thirty
+🕑 two o’clock
+🕝 two-thirty
+🕒 three o’clock
+🕞 three-thirty
+🕓 four o’clock
+🕟 four-thirty
+🕔 five o’clock
+🕠 five-thirty
+🕕 six o’clock
+🕡 six-thirty
+🕖 seven o’clock
+🕢 seven-thirty
+🕗 eight o’clock
+🕣 eight-thirty
+🕘 nine o’clock
+🕤 nine-thirty
+🕙 ten o’clock
+🕥 ten-thirty
+🕚 eleven o’clock
+🕦 eleven-thirty
+🌑 new moon
+🌒 waxing crescent moon
+🌓 first quarter moon
+🌔 waxing gibbous moon
+🌕 full moon
+🌖 waning gibbous moon
+🌗 last quarter moon
+🌘 waning crescent moon
+🌙 crescent moon
+🌚 new moon face
+🌛 first quarter moon face
+🌜 last quarter moon face
+🌡️ thermometer
+☀️ sun
+🌝 full moon face
+🌞 sun with face
+🪐 ringed planet
+⭐ star
+🌟 glowing star
+🌠 shooting star
+🌌 milky way
+☁️ cloud
+⛅ sun behind cloud
+⛈️ cloud with lightning and rain
+🌤️ sun behind small cloud
+🌥️ sun behind large cloud
+🌦️ sun behind rain cloud
+🌧️ cloud with rain
+🌨️ cloud with snow
+🌩️ cloud with lightning
+🌪️ tornado
+🌫️ fog
+🌬️ wind face
+🌀 cyclone
+🌈 rainbow
+🌂 closed umbrella
+☂️ umbrella
+☔ umbrella with rain drops
+⛱️ umbrella on ground
+⚡ high voltage
+❄️ snowflake
+☃️ snowman
+⛄ snowman without snow
+☄️ comet
+🔥 fire
+💧 droplet
+🌊 water wave
+🎃 jack-o-lantern
+🎄 Christmas tree
+🎆 fireworks
+🎇 sparkler
+🧨 firecracker
+✨ sparkles
+🎈 balloon
+🎉 party popper
+🎊 confetti ball
+🎋 tanabata tree
+🎍 pine decoration
+🎎 Japanese dolls
+🎏 carp streamer
+🎐 wind chime
+🎑 moon viewing ceremony
+🧧 red envelope
+🎀 ribbon
+🎁 wrapped gift
+🎗️ reminder ribbon
+🎟️ admission tickets
+🎫 ticket
+🎖️ military medal
+🏆 trophy
+🏅 sports medal
+🥇 1st place medal
+🥈 2nd place medal
+🥉 3rd place medal
+⚽ soccer ball
+⚾ baseball
+🥎 softball
+🏀 basketball
+🏐 volleyball
+🏈 american football
+🏉 rugby football
+🎾 tennis
+🥏 flying disc
+🎳 bowling
+🏏 cricket game
+🏑 field hockey
+🏒 ice hockey
+🥍 lacrosse
+🏓 ping pong
+🏸 badminton
+🥊 boxing glove
+🥋 martial arts uniform
+🥅 goal net
+⛳ flag in hole
+⛸️ ice skate
+🎣 fishing pole
+🤿 diving mask
+🎽 running shirt
+🎿 skis
+🛷 sled
+🥌 curling stone
+🎯 bullseye
+🪀 yo-yo
+🪁 kite
+🔫 water pistol
+🎱 pool 8 ball
+🔮 crystal ball
+🪄 magic wand
+🎮 video game
+🕹️ joystick
+🎰 slot machine
+🎲 game die
+🧩 puzzle piece
+🧸 teddy bear
+🪅 piñata
+🪩 mirror ball
+🪆 nesting dolls
+♠️ spade suit
+♥️ heart suit
+♦️ diamond suit
+♣️ club suit
+♟️ chess pawn
+🃏 joker
+🀄 mahjong red dragon
+🎴 flower playing cards
+🎭 performing arts
+🖼️ framed picture
+🎨 artist palette
+🧵 thread
+🪡 sewing needle
+🧶 yarn
+🪢 knot
+👓 glasses
+🕶️ sunglasses
+🥽 goggles
+🥼 lab coat
+🦺 safety vest
+👔 necktie
+👕 t-shirt
+👖 jeans
+🧣 scarf
+🧤 gloves
+🧥 coat
+🧦 socks
+👗 dress
+👘 kimono
+🥻 sari
+🩱 one-piece swimsuit
+🩲 briefs
+🩳 shorts
+👙 bikini
+👚 woman’s clothes
+🪭 folding hand fan
+👛 purse
+👜 handbag
+👝 clutch bag
+🛍️ shopping bags
+🎒 backpack
+🩴 thong sandal
+👞 man’s shoe
+👟 running shoe
+🥾 hiking boot
+🥿 flat shoe
+👠 high-heeled shoe
+👡 woman’s sandal
+🩰 ballet shoes
+👢 woman’s boot
+🪮 hair pick
+👑 crown
+👒 woman’s hat
+🎩 top hat
+🎓 graduation cap
+🧢 billed cap
+🪖 military helmet
+⛑️ rescue worker’s helmet
+📿 prayer beads
+💄 lipstick
+💍 ring
+💎 gem stone
+🔇 muted speaker
+🔈 speaker low volume
+🔉 speaker medium volume
+🔊 speaker high volume
+📢 loudspeaker
+📣 megaphone
+📯 postal horn
+🔔 bell
+🔕 bell with slash
+🎼 musical score
+🎵 musical note
+🎶 musical notes
+🎙️ studio microphone
+🎚️ level slider
+🎛️ control knobs
+🎤 microphone
+🎧 headphone
+📻 radio
+🎷 saxophone
+🪗 accordion
+🎸 guitar
+🎹 musical keyboard
+🎺 trumpet
+🎻 violin
+🪕 banjo
+🥁 drum
+🪘 long drum
+🪇 maracas
+🪈 flute
+🪉 harp
+📱 mobile phone
+📲 mobile phone with arrow
+☎️ telephone
+📞 telephone receiver
+📟 pager
+📠 fax machine
+🔋 battery
+🪫 low battery
+🔌 electric plug
+💻 laptop
+🖥️ desktop computer
+🖨️ printer
+⌨️ keyboard
+🖱️ computer mouse
+🖲️ trackball
+💽 computer disk
+💾 floppy disk
+💿 optical disk
+📀 dvd
+🧮 abacus
+🎥 movie camera
+🎞️ film frames
+📽️ film projector
+🎬 clapper board
+📺 television
+📷 camera
+📸 camera with flash
+📹 video camera
+📼 videocassette
+🔍 magnifying glass tilted left
+🔎 magnifying glass tilted right
+🕯️ candle
+💡 light bulb
+🔦 flashlight
+🏮 red paper lantern
+🪔 diya lamp
+📔 notebook with decorative cover
+📕 closed book
+📖 open book
+📗 green book
+📘 blue book
+📙 orange book
+📚 books
+📓 notebook
+📒 ledger
+📃 page with curl
+📜 scroll
+📄 page facing up
+📰 newspaper
+🗞️ rolled-up newspaper
+📑 bookmark tabs
+🔖 bookmark
+🏷️ label
+💰 money bag
+🪙 coin
+💴 yen banknote
+💵 dollar banknote
+💶 euro banknote
+💷 pound banknote
+💸 money with wings
+💳 credit card
+🧾 receipt
+💹 chart increasing with yen
+✉️ envelope
+📧 e-mail
+📨 incoming envelope
+📩 envelope with arrow
+📤 outbox tray
+📥 inbox tray
+📦 package
+📫 closed mailbox with raised flag
+📪 closed mailbox with lowered flag
+📬 open mailbox with raised flag
+📭 open mailbox with lowered flag
+📮 postbox
+🗳️ ballot box with ballot
+✏️ pencil
+✒️ black nib
+🖋️ fountain pen
+🖊️ pen
+🖌️ paintbrush
+🖍️ crayon
+📝 memo
+💼 briefcase
+📁 file folder
+📂 open file folder
+🗂️ card index dividers
+📅 calendar
+📆 tear-off calendar
+🗒️ spiral notepad
+🗓️ spiral calendar
+📇 card index
+📈 chart increasing
+📉 chart decreasing
+📊 bar chart
+📋 clipboard
+📌 pushpin
+📍 round pushpin
+📎 paperclip
+🖇️ linked paperclips
+📏 straight ruler
+📐 triangular ruler
+✂️ scissors
+🗃️ card file box
+🗄️ file cabinet
+🗑️ wastebasket
+🔒 locked
+🔓 unlocked
+🔏 locked with pen
+🔐 locked with key
+🔑 key
+🗝️ old key
+🔨 hammer
+🪓 axe
+⛏️ pick
+⚒️ hammer and pick
+🛠️ hammer and wrench
+🗡️ dagger
+⚔️ crossed swords
+💣 bomb
+🪃 boomerang
+🏹 bow and arrow
+🛡️ shield
+🪚 carpentry saw
+🔧 wrench
+🪛 screwdriver
+🔩 nut and bolt
+⚙️ gear
+🗜️ clamp
+⚖️ balance scale
+🦯 white cane
+🔗 link
+⛓️ chains
+🪝 hook
+🧰 toolbox
+🧲 magnet
+🪜 ladder
+🪏 shovel
+⚗️ alembic
+🧪 test tube
+🧫 petri dish
+🧬 dna
+🔬 microscope
+🔭 telescope
+📡 satellite antenna
+💉 syringe
+🩸 drop of blood
+💊 pill
+🩹 adhesive bandage
+🩼 crutch
+🩺 stethoscope
+🩻 x-ray
+🚪 door
+🛗 elevator
+🪞 mirror
+🪟 window
+🛏️ bed
+🛋️ couch and lamp
+🪑 chair
+🚽 toilet
+🪠 plunger
+🚿 shower
+🛁 bathtub
+🪤 mouse trap
+🪒 razor
+🧴 lotion bottle
+🧷 safety pin
+🧹 broom
+🧺 basket
+🧻 roll of paper
+🪣 bucket
+🧼 soap
+🫧 bubbles
+🪥 toothbrush
+🧽 sponge
+🧯 fire extinguisher
+🛒 shopping cart
+🚬 cigarette
+⚰️ coffin
+🪦 headstone
+⚱️ funeral urn
+🧿 nazar amulet
+🪬 hamsa
+🗿 moai
+🪧 placard
+🪪 identification card
+🏧 ATM sign
+🚮 litter in bin sign
+🚰 potable water
+♿ wheelchair symbol
+🚹 men’s room
+🚺 women’s room
+🚻 restroom
+🚼 baby symbol
+🚾 water closet
+🛂 passport control
+🛃 customs
+🛄 baggage claim
+🛅 left luggage
+⚠️ warning
+🚸 children crossing
+⛔ no entry
+🚫 prohibited
+🚳 no bicycles
+🚭 no smoking
+🚯 no littering
+🚱 non-potable water
+🚷 no pedestrians
+📵 no mobile phones
+🔞 no one under eighteen
+☢️ radioactive
+☣️ biohazard
+⬆️ up arrow
+↗️ up-right arrow
+➡️ right arrow
+↘️ down-right arrow
+⬇️ down arrow
+↙️ down-left arrow
+⬅️ left arrow
+↖️ up-left arrow
+↕️ up-down arrow
+↔️ left-right arrow
+↩️ right arrow curving left
+↪️ left arrow curving right
+⤴️ right arrow curving up
+⤵️ right arrow curving down
+🔃 clockwise vertical arrows
+🔄 counterclockwise arrows button
+🔙 BACK arrow
+🔚 END arrow
+🔛 ON! arrow
+🔜 SOON arrow
+🔝 TOP arrow
+🛐 place of worship
+⚛️ atom symbol
+🕉️ om
+✡️ star of David
+☸️ wheel of dharma
+☯️ yin yang
+✝️ latin cross
+☦️ orthodox cross
+☪️ star and crescent
+☮️ peace symbol
+🕎 menorah
+🔯 dotted six-pointed star
+🪯 khanda
+♈ Aries
+♉ Taurus
+♊ Gemini
+♋ Cancer
+♌ Leo
+♍ Virgo
+♎ Libra
+♏ Scorpio
+♐ Sagittarius
+♑ Capricorn
+♒ Aquarius
+♓ Pisces
+⛎ Ophiuchus
+🔀 shuffle tracks button
+🔁 repeat button
+🔂 repeat single button
+▶️ play button
+⏩ fast-forward button
+⏭️ next track button
+⏯️ play or pause button
+◀️ reverse button
+⏪ fast reverse button
+⏮️ last track button
+🔼 upwards button
+⏫ fast up button
+🔽 downwards button
+⏬ fast down button
+⏸️ pause button
+⏹️ stop button
+⏺️ record button
+⏏️ eject button
+🎦 cinema
+🔅 dim button
+🔆 bright button
+📶 antenna bars
+🛜 wireless
+📳 vibration mode
+📴 mobile phone off
+♀️ female sign
+♂️ male sign
+⚧️ transgender symbol
+✖️ multiply
+➕ plus
+➖ minus
+➗ divide
+🟰 heavy equals sign
+♾️ infinity
+‼️ double exclamation mark
+⁉️ exclamation question mark
+❓ red question mark
+❔ white question mark
+❕ white exclamation mark
+❗ red exclamation mark
+〰️ wavy dash
+💱 currency exchange
+💲 heavy dollar sign
+⚕️ medical symbol
+♻️ recycling symbol
+⚜️ fleur-de-lis
+🔱 trident emblem
+📛 name badge
+🔰 Japanese symbol for beginner
+⭕ hollow red circle
+✅ check mark button
+☑️ check box with check
+✔️ check mark
+❌ cross mark
+❎ cross mark button
+➰ curly loop
+➿ double curly loop
+〽️ part alternation mark
+✳️ eight-spoked asterisk
+✴️ eight-pointed star
+❇️ sparkle
+©️ copyright
+®️ registered
+™️ trade mark
+🫟 splatter
+#️⃣ keycap: #
+*️⃣ keycap: *
+0️⃣ keycap: 0
+1️⃣ keycap: 1
+2️⃣ keycap: 2
+3️⃣ keycap: 3
+4️⃣ keycap: 4
+5️⃣ keycap: 5
+6️⃣ keycap: 6
+7️⃣ keycap: 7
+8️⃣ keycap: 8
+9️⃣ keycap: 9
+🔟 keycap: 10
+🔠 input latin uppercase
+🔡 input latin lowercase
+🔢 input numbers
+🔣 input symbols
+🔤 input latin letters
+🅰️ A button (blood type)
+🆎 AB button (blood type)
+🅱️ B button (blood type)
+🆑 CL button
+🆒 COOL button
+🆓 FREE button
+ℹ️ information
+🆔 ID button
+Ⓜ️ circled M
+🆕 NEW button
+🆖 NG button
+🅾️ O button (blood type)
+🆗 OK button
+🅿️ P button
+🆘 SOS button
+🆙 UP! button
+🆚 VS button
+🈁 Japanese “here” button
+🈂️ Japanese “service charge” button
+🈷️ Japanese “monthly amount” button
+🈶 Japanese “not free of charge” button
+🈯 Japanese “reserved” button
+🉐 Japanese “bargain” button
+🈹 Japanese “discount” button
+🈚 Japanese “free of charge” button
+🈲 Japanese “prohibited” button
+🉑 Japanese “acceptable” button
+🈸 Japanese “application” button
+🈴 Japanese “passing grade” button
+🈳 Japanese “vacancy” button
+㊗️ Japanese “congratulations” button
+㊙️ Japanese “secret” button
+🈺 Japanese “open for business” button
+🈵 Japanese “no vacancy” button
+🔴 red circle
+🟠 orange circle
+🟡 yellow circle
+🟢 green circle
+🔵 blue circle
+🟣 purple circle
+🟤 brown circle
+⚫ black circle
+⚪ white circle
+🟥 red square
+🟧 orange square
+🟨 yellow square
+🟩 green square
+🟦 blue square
+🟪 purple square
+🟫 brown square
+⬛ black large square
+⬜ white large square
+◼️ black medium square
+◻️ white medium square
+◾ black medium-small square
+◽ white medium-small square
+▪️ black small square
+▫️ white small square
+🔶 large orange diamond
+🔷 large blue diamond
+🔸 small orange diamond
+🔹 small blue diamond
+🔺 red triangle pointed up
+🔻 red triangle pointed down
+💠 diamond with a dot
+🔘 radio button
+🔳 white square button
+🔲 black square button
+🏁 chequered flag
+🚩 triangular flag
+🎌 crossed flags
+🏴 black flag
+🏳️ white flag
+🇦🇨 flag: Ascension Island
+🇦🇩 flag: Andorra
+🇦🇪 flag: United Arab Emirates
+🇦🇫 flag: Afghanistan
+🇦🇬 flag: Antigua & Barbuda
+🇦🇮 flag: Anguilla
+🇦🇱 flag: Albania
+🇦🇲 flag: Armenia
+🇦🇴 flag: Angola
+🇦🇶 flag: Antarctica
+🇦🇷 flag: Argentina
+🇦🇸 flag: American Samoa
+🇦🇹 flag: Austria
+🇦🇺 flag: Australia
+🇦🇼 flag: Aruba
+🇦🇽 flag: Åland Islands
+🇦🇿 flag: Azerbaijan
+🇧🇦 flag: Bosnia & Herzegovina
+🇧🇧 flag: Barbados
+🇧🇩 flag: Bangladesh
+🇧🇪 flag: Belgium
+🇧🇫 flag: Burkina Faso
+🇧🇬 flag: Bulgaria
+🇧🇭 flag: Bahrain
+🇧🇮 flag: Burundi
+🇧🇯 flag: Benin
+🇧🇱 flag: St. Barthélemy
+🇧🇲 flag: Bermuda
+🇧🇳 flag: Brunei
+🇧🇴 flag: Bolivia
+🇧🇶 flag: Caribbean Netherlands
+🇧🇷 flag: Brazil
+🇧🇸 flag: Bahamas
+🇧🇹 flag: Bhutan
+🇧🇻 flag: Bouvet Island
+🇧🇼 flag: Botswana
+🇧🇾 flag: Belarus
+🇧🇿 flag: Belize
+🇨🇦 flag: Canada
+🇨🇨 flag: Cocos (Keeling) Islands
+🇨🇩 flag: Congo - Kinshasa
+🇨🇫 flag: Central African Republic
+🇨🇬 flag: Congo - Brazzaville
+🇨🇭 flag: Switzerland
+🇨🇮 flag: Côte d’Ivoire
+🇨🇰 flag: Cook Islands
+🇨🇱 flag: Chile
+🇨🇲 flag: Cameroon
+🇨🇳 flag: China
+🇨🇴 flag: Colombia
+🇨🇵 flag: Clipperton Island
+🇨🇶 flag: Sark
+🇨🇷 flag: Costa Rica
+🇨🇺 flag: Cuba
+🇨🇻 flag: Cape Verde
+🇨🇼 flag: Curaçao
+🇨🇽 flag: Christmas Island
+🇨🇾 flag: Cyprus
+🇨🇿 flag: Czechia
+🇩🇪 flag: Germany
+🇩🇬 flag: Diego Garcia
+🇩🇯 flag: Djibouti
+🇩🇰 flag: Denmark
+🇩🇲 flag: Dominica
+🇩🇴 flag: Dominican Republic
+🇩🇿 flag: Algeria
+🇪🇦 flag: Ceuta & Melilla
+🇪🇨 flag: Ecuador
+🇪🇪 flag: Estonia
+🇪🇬 flag: Egypt
+🇪🇭 flag: Western Sahara
+🇪🇷 flag: Eritrea
+🇪🇸 flag: Spain
+🇪🇹 flag: Ethiopia
+🇪🇺 flag: European Union
+🇫🇮 flag: Finland
+🇫🇯 flag: Fiji
+🇫🇰 flag: Falkland Islands
+🇫🇲 flag: Micronesia
+🇫🇴 flag: Faroe Islands
+🇫🇷 flag: France
+🇬🇦 flag: Gabon
+🇬🇧 flag: United Kingdom
+🇬🇩 flag: Grenada
+🇬🇪 flag: Georgia
+🇬🇫 flag: French Guiana
+🇬🇬 flag: Guernsey
+🇬🇭 flag: Ghana
+🇬🇮 flag: Gibraltar
+🇬🇱 flag: Greenland
+🇬🇲 flag: Gambia
+🇬🇳 flag: Guinea
+🇬🇵 flag: Guadeloupe
+🇬🇶 flag: Equatorial Guinea
+🇬🇷 flag: Greece
+🇬🇸 flag: South Georgia & South Sandwich Islands
+🇬🇹 flag: Guatemala
+🇬🇺 flag: Guam
+🇬🇼 flag: Guinea-Bissau
+🇬🇾 flag: Guyana
+🇭🇰 flag: Hong Kong SAR China
+🇭🇲 flag: Heard & McDonald Islands
+🇭🇳 flag: Honduras
+🇭🇷 flag: Croatia
+🇭🇹 flag: Haiti
+🇭🇺 flag: Hungary
+🇮🇨 flag: Canary Islands
+🇮🇩 flag: Indonesia
+🇮🇪 flag: Ireland
+🇮🇱 flag: Israel
+🇮🇲 flag: Isle of Man
+🇮🇳 flag: India
+🇮🇴 flag: British Indian Ocean Territory
+🇮🇶 flag: Iraq
+🇮🇷 flag: Iran
+🇮🇸 flag: Iceland
+🇮🇹 flag: Italy
+🇯🇪 flag: Jersey
+🇯🇲 flag: Jamaica
+🇯🇴 flag: Jordan
+🇯🇵 flag: Japan
+🇰🇪 flag: Kenya
+🇰🇬 flag: Kyrgyzstan
+🇰🇭 flag: Cambodia
+🇰🇮 flag: Kiribati
+🇰🇲 flag: Comoros
+🇰🇳 flag: St. Kitts & Nevis
+🇰🇵 flag: North Korea
+🇰🇷 flag: South Korea
+🇰🇼 flag: Kuwait
+🇰🇾 flag: Cayman Islands
+🇰🇿 flag: Kazakhstan
+🇱🇦 flag: Laos
+🇱🇧 flag: Lebanon
+🇱🇨 flag: St. Lucia
+🇱🇮 flag: Liechtenstein
+🇱🇰 flag: Sri Lanka
+🇱🇷 flag: Liberia
+🇱🇸 flag: Lesotho
+🇱🇹 flag: Lithuania
+🇱🇺 flag: Luxembourg
+🇱🇻 flag: Latvia
+🇱🇾 flag: Libya
+🇲🇦 flag: Morocco
+🇲🇨 flag: Monaco
+🇲🇩 flag: Moldova
+🇲🇪 flag: Montenegro
+🇲🇫 flag: St. Martin
+🇲🇬 flag: Madagascar
+🇲🇭 flag: Marshall Islands
+🇲🇰 flag: North Macedonia
+🇲🇱 flag: Mali
+🇲🇲 flag: Myanmar (Burma)
+🇲🇳 flag: Mongolia
+🇲🇴 flag: Macao SAR China
+🇲🇵 flag: Northern Mariana Islands
+🇲🇶 flag: Martinique
+🇲🇷 flag: Mauritania
+🇲🇸 flag: Montserrat
+🇲🇹 flag: Malta
+🇲🇺 flag: Mauritius
+🇲🇻 flag: Maldives
+🇲🇼 flag: Malawi
+🇲🇽 flag: Mexico
+🇲🇾 flag: Malaysia
+🇲🇿 flag: Mozambique
+🇳🇦 flag: Namibia
+🇳🇨 flag: New Caledonia
+🇳🇪 flag: Niger
+🇳🇫 flag: Norfolk Island
+🇳🇬 flag: Nigeria
+🇳🇮 flag: Nicaragua
+🇳🇱 flag: Netherlands
+🇳🇴 flag: Norway
+🇳🇵 flag: Nepal
+🇳🇷 flag: Nauru
+🇳🇺 flag: Niue
+🇳🇿 flag: New Zealand
+🇴🇲 flag: Oman
+🇵🇦 flag: Panama
+🇵🇪 flag: Peru
+🇵🇫 flag: French Polynesia
+🇵🇬 flag: Papua New Guinea
+🇵🇭 flag: Philippines
+🇵🇰 flag: Pakistan
+🇵🇱 flag: Poland
+🇵🇲 flag: St. Pierre & Miquelon
+🇵🇳 flag: Pitcairn Islands
+🇵🇷 flag: Puerto Rico
+🇵🇸 flag: Palestinian Territories
+🇵🇹 flag: Portugal
+🇵🇼 flag: Palau
+🇵🇾 flag: Paraguay
+🇶🇦 flag: Qatar
+🇷🇪 flag: Réunion
+🇷🇴 flag: Romania
+🇷🇸 flag: Serbia
+🇷🇺 flag: Russia
+🇷🇼 flag: Rwanda
+🇸🇦 flag: Saudi Arabia
+🇸🇧 flag: Solomon Islands
+🇸🇨 flag: Seychelles
+🇸🇩 flag: Sudan
+🇸🇪 flag: Sweden
+🇸🇬 flag: Singapore
+🇸🇭 flag: St. Helena
+🇸🇮 flag: Slovenia
+🇸🇯 flag: Svalbard & Jan Mayen
+🇸🇰 flag: Slovakia
+🇸🇱 flag: Sierra Leone
+🇸🇲 flag: San Marino
+🇸🇳 flag: Senegal
+🇸🇴 flag: Somalia
+🇸🇷 flag: Suriname
+🇸🇸 flag: South Sudan
+🇸🇹 flag: São Tomé & Príncipe
+🇸🇻 flag: El Salvador
+🇸🇽 flag: Sint Maarten
+🇸🇾 flag: Syria
+🇸🇿 flag: Eswatini
+🇹🇦 flag: Tristan da Cunha
+🇹🇨 flag: Turks & Caicos Islands
+🇹🇩 flag: Chad
+🇹🇫 flag: French Southern Territories
+🇹🇬 flag: Togo
+🇹🇭 flag: Thailand
+🇹🇯 flag: Tajikistan
+🇹🇰 flag: Tokelau
+🇹🇱 flag: Timor-Leste
+🇹🇲 flag: Turkmenistan
+🇹🇳 flag: Tunisia
+🇹🇴 flag: Tonga
+🇹🇷 flag: Türkiye
+🇹🇹 flag: Trinidad & Tobago
+🇹🇻 flag: Tuvalu
+🇹🇼 flag: Taiwan
+🇹🇿 flag: Tanzania
+🇺🇦 flag: Ukraine
+🇺🇬 flag: Uganda
+🇺🇲 flag: U.S. Outlying Islands
+🇺🇳 flag: United Nations
+🇺🇸 flag: United States
+🇺🇾 flag: Uruguay
+🇺🇿 flag: Uzbekistan
+🇻🇦 flag: Vatican City
+🇻🇨 flag: St. Vincent & Grenadines
+🇻🇪 flag: Venezuela
+🇻🇬 flag: British Virgin Islands
+🇻🇮 flag: U.S. Virgin Islands
+🇻🇳 flag: Vietnam
+🇻🇺 flag: Vanuatu
+🇼🇫 flag: Wallis & Futuna
+🇼🇸 flag: Samoa
+🇽🇰 flag: Kosovo
+🇾🇪 flag: Yemen
+🇾🇹 flag: Mayotte
+🇿🇦 flag: South Africa
+🇿🇲 flag: Zambia
+🇿🇼 flag: Zimbabwe
+🏴󠁧󠁢󠁥󠁮󠁧󠁿 flag: England
+🏴󠁧󠁢󠁳󠁣󠁴󠁿 flag: Scotland
+🏴󠁧󠁢󠁷󠁬󠁳󠁿 flag: Wales
diff --git a/default/.local/share/thesiah/chars/font-awesome b/default/.local/share/thesiah/chars/font-awesome
new file mode 100644
index 0000000..3283be3
--- /dev/null
+++ b/default/.local/share/thesiah/chars/font-awesome
@@ -0,0 +1,1456 @@
+ 500px; f26e
+ accessible-icon; f368
+ accusoft; f369
+ acquisitions-incorporated; f6af
+ ad; f641
+ address-book; f2b9
+ address-card; f2bb
+ adjust; f042
+ adn; f170
+ adversal; f36a
+ affiliatetheme; f36b
+ air-freshener; f5d0
+ airbnb; f834
+ algolia; f36c
+ align-center; f037
+ align-justify; f039
+ align-left; f036
+ align-right; f038
+ alipay; f642
+ allergies; f461
+ amazon; f270
+ amazon-pay; f42c
+ ambulance; f0f9
+ american-sign-language-interpreting; f2a3
+ amilia; f36d
+ anchor; f13d
+ android; f17b
+ angellist; f209
+ angle-double-down; f103
+ angle-double-left; f100
+ angle-double-right; f101
+ angle-double-up; f102
+ angle-down; f107
+ angle-left; f104
+ angle-right; f105
+ angle-up; f106
+ angry; f556
+ angrycreative; f36e
+ angular; f420
+ ankh; f644
+ app-store; f36f
+ app-store-ios; f370
+ apper; f371
+ apple; f179
+ apple-alt; f5d1
+ apple-pay; f415
+ archive; f187
+ archway; f557
+ arrow-alt-circle-down; f358
+ arrow-alt-circle-left; f359
+ arrow-alt-circle-right; f35a
+ arrow-alt-circle-up; f35b
+ arrow-circle-down; f0ab
+ arrow-circle-left; f0a8
+ arrow-circle-right; f0a9
+ arrow-circle-up; f0aa
+ arrow-down; f063
+ arrow-left; f060
+ arrow-right; f061
+ arrow-up; f062
+ arrows-alt; f0b2
+ arrows-alt-h; f337
+ arrows-alt-v; f338
+ artstation; f77a
+ assistive-listening-systems; f2a2
+ asterisk; f069
+ asymmetrik; f372
+ at; f1fa
+ atlas; f558
+ atlassian; f77b
+ atom; f5d2
+ audible; f373
+ audio-description; f29e
+ autoprefixer; f41c
+ avianex; f374
+ aviato; f421
+ award; f559
+ aws; f375
+ baby; f77c
+ baby-carriage; f77d
+ backspace; f55a
+ backward; f04a
+ bacon; f7e5
+ bacteria e059
+ bacterium e05a
+ bahai; f666
+ balance-scale; f24e
+ balance-scale-left; f515
+ balance-scale-right; f516
+ ban; f05e
+ band-aid; f462
+ bandcamp; f2d5
+ barcode; f02a
+ bars; f0c9
+ baseball-ball; f433
+ basketball-ball; f434
+ bath; f2cd
+ battery-empty; f244
+ battery-full; f240
+ battery-half; f242
+ battery-quarter; f243
+ battery-three-quarters; f241
+ battle-net; f835
+ bed; f236
+ beer; f0fc
+ behance; f1b4
+ behance-square; f1b5
+ bell; f0f3
+ bell-slash; f1f6
+ bezier-curve; f55b
+ bible; f647
+ bicycle; f206
+ biking; f84a
+ bimobject; f378
+ binoculars; f1e5
+ biohazard; f780
+ birthday-cake; f1fd
+ bitbucket; f171
+ bitcoin; f379
+ bity; f37a
+ black-tie; f27e
+ blackberry; f37b
+ blender; f517
+ blender-phone; f6b6
+ blind; f29d
+ blog; f781
+ blogger; f37c
+ blogger-b; f37d
+ bluetooth; f293
+ bluetooth-b; f294
+ bold; f032
+ bolt; f0e7
+ bomb; f1e2
+ bone; f5d7
+ bong; f55c
+ book; f02d
+ book-dead; f6b7
+ book-medical; f7e6
+ book-open; f518
+ book-reader; f5da
+ bookmark; f02e
+ bootstrap; f836
+ border-all; f84c
+ border-none; f850
+ border-style; f853
+ bowling-ball; f436
+ box; f466
+ box-open; f49e
+ box-tissue e05b
+ boxes; f468
+ braille; f2a1
+ brain; f5dc
+ bread-slice; f7ec
+ briefcase; f0b1
+ briefcase-medical; f469
+ broadcast-tower; f519
+ broom; f51a
+ brush; f55d
+ btc; f15a
+ buffer; f837
+ bug; f188
+ building; f1ad
+ bullhorn; f0a1
+ bullseye; f140
+ burn; f46a
+ buromobelexperte; f37f
+ bus; f207
+ bus-alt; f55e
+ business-time; f64a
+ buy-n-large; f8a6
+ calculator; f1ec
+ calendar; f133
+ calendar-alt; f073
+ calendar-check; f274
+ calendar-day; f783
+ calendar-minus; f272
+ calendar-plus; f271
+ calendar-times; f273
+ calendar-week; f784
+ camera; f030
+ camera-retro; f083
+ campground; f6bb
+ canadian-maple-leaf; f785
+ candy-cane; f786
+ cannabis; f55f
+ capsules; f46b
+ car; f1b9
+ car-alt; f5de
+ car-battery; f5df
+ car-crash; f5e1
+ car-side; f5e4
+ caravan; f8ff
+ caret-down; f0d7
+ caret-left; f0d9
+ caret-right; f0da
+ caret-square-down; f150
+ caret-square-left; f191
+ caret-square-right; f152
+ caret-square-up; f151
+ caret-up; f0d8
+ carrot; f787
+ cart-arrow-down; f218
+ cart-plus; f217
+ cash-register; f788
+ cat; f6be
+ cc-amazon-pay; f42d
+ cc-amex; f1f3
+ cc-apple-pay; f416
+ cc-diners-club; f24c
+ cc-discover; f1f2
+ cc-jcb; f24b
+ cc-mastercard; f1f1
+ cc-paypal; f1f4
+ cc-stripe; f1f5
+ cc-visa; f1f0
+ centercode; f380
+ centos; f789
+ certificate; f0a3
+ chair; f6c0
+ chalkboard; f51b
+ chalkboard-teacher; f51c
+ charging-station; f5e7
+ chart-area; f1fe
+ chart-bar; f080
+ chart-line; f201
+ chart-pie; f200
+ check; f00c
+ check-circle; f058
+ check-double; f560
+ check-square; f14a
+ cheese; f7ef
+ chess; f439
+ chess-bishop; f43a
+ chess-board; f43c
+ chess-king; f43f
+ chess-knight; f441
+ chess-pawn; f443
+ chess-queen; f445
+ chess-rook; f447
+ chevron-circle-down; f13a
+ chevron-circle-left; f137
+ chevron-circle-right; f138
+ chevron-circle-up; f139
+ chevron-down; f078
+ chevron-left; f053
+ chevron-right; f054
+ chevron-up; f077
+ child; f1ae
+ chrome; f268
+ chromecast; f838
+ church; f51d
+ circle; f111
+ circle-notch; f1ce
+ city; f64f
+ clinic-medical; f7f2
+ clipboard; f328
+ clipboard-check; f46c
+ clipboard-list; f46d
+ clock; f017
+ clone; f24d
+ closed-captioning; f20a
+ cloud; f0c2
+ cloud-download-alt; f381
+ cloud-meatball; f73b
+ cloud-moon; f6c3
+ cloud-moon-rain; f73c
+ cloud-rain; f73d
+ cloud-showers-heavy; f740
+ cloud-sun; f6c4
+ cloud-sun-rain; f743
+ cloud-upload-alt; f382
+ cloudflare e07d
+ cloudscale; f383
+ cloudsmith; f384
+ cloudversify; f385
+ cocktail; f561
+ code; f121
+ code-branch; f126
+ codepen; f1cb
+ codiepie; f284
+ coffee; f0f4
+ cog; f013
+ cogs; f085
+ coins; f51e
+ columns; f0db
+ comment; f075
+ comment-alt; f27a
+ comment-dollar; f651
+ comment-dots; f4ad
+ comment-medical; f7f5
+ comment-slash; f4b3
+ comments; f086
+ comments-dollar; f653
+ compact-disc; f51f
+ compass; f14e
+ compress; f066
+ compress-alt; f422
+ compress-arrows-alt; f78c
+ concierge-bell; f562
+ confluence; f78d
+ connectdevelop; f20e
+ contao; f26d
+ cookie; f563
+ cookie-bite; f564
+ copy; f0c5
+ copyright; f1f9
+ cotton-bureau; f89e
+ couch; f4b8
+ cpanel; f388
+ creative-commons; f25e
+ creative-commons-by; f4e7
+ creative-commons-nc; f4e8
+ creative-commons-nc-eu; f4e9
+ creative-commons-nc-jp; f4ea
+ creative-commons-nd; f4eb
+ creative-commons-pd; f4ec
+ creative-commons-pd-alt; f4ed
+ creative-commons-remix; f4ee
+ creative-commons-sa; f4ef
+ creative-commons-sampling; f4f0
+ creative-commons-sampling-plus; f4f1
+ creative-commons-share; f4f2
+ creative-commons-zero; f4f3
+ credit-card; f09d
+ critical-role; f6c9
+ crop; f125
+ crop-alt; f565
+ cross; f654
+ crosshairs; f05b
+ crow; f520
+ crown; f521
+ crutch; f7f7
+ css3; f13c
+ css3-alt; f38b
+ cube; f1b2
+ cubes; f1b3
+ cut; f0c4
+ cuttlefish; f38c
+ d-and-d; f38d
+ d-and-d-beyond; f6ca
+ dailymotion e052
+ dashcube; f210
+ database; f1c0
+ deaf; f2a4
+ deezer e077
+ delicious; f1a5
+ democrat; f747
+ deploydog; f38e
+ deskpro; f38f
+ desktop; f108
+ dev; f6cc
+ deviantart; f1bd
+ dharmachakra; f655
+ dhl; f790
+ diagnoses; f470
+ diaspora; f791
+ dice; f522
+ dice-d20; f6cf
+ dice-d6; f6d1
+ dice-five; f523
+ dice-four; f524
+ dice-one; f525
+ dice-six; f526
+ dice-three; f527
+ dice-two; f528
+ digg; f1a6
+ digital-ocean; f391
+ digital-tachograph; f566
+ directions; f5eb
+ discord; f392
+ discourse; f393
+ disease; f7fa
+ divide; f529
+ dizzy; f567
+ dna; f471
+ dochub; f394
+ docker; f395
+ dog; f6d3
+ dollar-sign; f155
+ dolly; f472
+ dolly-flatbed; f474
+ donate; f4b9
+ door-closed; f52a
+ door-open; f52b
+ dot-circle; f192
+ dove; f4ba
+ download; f019
+ draft2digital; f396
+ drafting-compass; f568
+ dragon; f6d5
+ draw-polygon; f5ee
+ dribbble; f17d
+ dribbble-square; f397
+ dropbox; f16b
+ drum; f569
+ drum-steelpan; f56a
+ drumstick-bite; f6d7
+ drupal; f1a9
+ dumbbell; f44b
+ dumpster; f793
+ dumpster-fire; f794
+ dungeon; f6d9
+ dyalog; f399
+ earlybirds; f39a
+ ebay; f4f4
+ edge; f282
+ edge-legacy e078
+ edit; f044
+ egg; f7fb
+ eject; f052
+ elementor; f430
+ ellipsis-h; f141
+ ellipsis-v; f142
+ ello; f5f1
+ ember; f423
+ empire; f1d1
+ envelope; f0e0
+ envelope-open; f2b6
+ envelope-open-text; f658
+ envelope-square; f199
+ envira; f299
+ equals; f52c
+ eraser; f12d
+ erlang; f39d
+ ethereum; f42e
+ ethernet; f796
+ etsy; f2d7
+ euro-sign; f153
+ evernote; f839
+ exchange-alt; f362
+ exclamation; f12a
+ exclamation-circle; f06a
+ exclamation-triangle; f071
+ expand; f065
+ expand-alt; f424
+ expand-arrows-alt; f31e
+ expeditedssl; f23e
+ external-link-alt; f35d
+ external-link-square-alt; f360
+ eye; f06e
+ eye-dropper; f1fb
+ eye-slash; f070
+ ; facebook; f09a
+ ; facebook-f; f39e
+ ; facebook-messenger; f39f
+ ; facebook-square; f082
+ ; fan; f863
+ ; fantasy-flight-games; f6dc
+ ; fast-backward; f049
+ ; fast-forward; f050
+ ; faucet e005
+ ; fax; f1ac
+ ; feather; f52d
+ ; feather-alt; f56b
+ ; fedex; f797
+ ; fedora; f798
+ ; female; f182
+ ; fighter-jet; f0fb
+ ; figma; f799
+ ; file; f15b
+ ; file-alt; f15c
+ ; file-archive; f1c6
+ ; file-audio; f1c7
+ ; file-code; f1c9
+ ; file-contract; f56c
+ ; file-csv; f6dd
+ ; file-download; f56d
+ ; file-excel; f1c3
+ ; file-export; f56e
+ ; file-image; f1c5
+ ; file-import; f56f
+ ; file-invoice; f570
+ ; file-invoice-dollar; f571
+ ; file-medical; f477
+ ; file-medical-alt; f478
+ ; file-pdf; f1c1
+ ; file-powerpoint; f1c4
+ ; file-prescription; f572
+ ; file-signature; f573
+ ; file-upload; f574
+ ; file-video; f1c8
+ ; file-word; f1c2
+ ; fill; f575
+ ; fill-drip; f576
+ ; film; f008
+ ; filter; f0b0
+ ; fingerprint; f577
+ ; fire; f06d
+ ; fire-alt; f7e4
+ ; fire-extinguisher; f134
+ ; firefox; f269
+ ; firefox-browser e007
+ ; first-aid; f479
+ ; first-order; f2b0
+ ; first-order-alt; f50a
+ ; firstdraft; f3a1
+ ; fish; f578
+ ; fist-raised; f6de
+ ; flag; f024
+ ; flag-checkered; f11e
+ ; flag-usa; f74d
+ ; flask; f0c3
+ ; flickr; f16e
+ ; flipboard; f44d
+ ; flushed; f579
+ ; fly; f417
+ ; folder; f07b
+ ; folder-minus; f65d
+ ; folder-open; f07c
+ ; folder-plus; f65e
+ ; font; f031
+ ; font-awesome; f2b4
+ ; font-awesome-alt; f35c
+ ; font-awesome-flag; f425
+ ; fonticons; f280
+ ; fonticons-fi; f3a2
+ ; football-ball; f44e
+ ; fort-awesome; f286
+ ; fort-awesome-alt; f3a3
+ ; forumbee; f211
+ ; forward; f04e
+ ; foursquare; f180
+ ; free-code-camp; f2c5
+ ; freebsd; f3a4
+ ; frog; f52e
+ ; frown; f119
+ ; frown-open; f57a
+ ; fulcrum; f50b
+ ; funnel-dollar; f662
+ ; futbol; f1e3
+ galactic-republic; f50c
+ galactic-senate; f50d
+ gamepad; f11b
+ gas-pump; f52f
+ gavel; f0e3
+ gem; f3a5
+ genderless; f22d
+ get-pocket; f265
+ gg; f260
+ gg-circle; f261
+ ghost; f6e2
+ gift; f06b
+ gifts; f79c
+ git; f1d3
+ git-alt; f841
+ git-square; f1d2
+ github; f09b
+ github-alt; f113
+ github-square; f092
+ gitkraken; f3a6
+ gitlab; f296
+ gitter; f426
+ glass-cheers; f79f
+ glass-martini; f000
+ glass-martini-alt; f57b
+ glass-whiskey; f7a0
+ glasses; f530
+ glide; f2a5
+ glide-g; f2a6
+ globe; f0ac
+ globe-africa; f57c
+ globe-americas; f57d
+ globe-asia; f57e
+ globe-europe; f7a2
+ gofore; f3a7
+ golf-ball; f450
+ goodreads; f3a8
+ goodreads-g; f3a9
+ google; f1a0
+ google-drive; f3aa
+ google-pay e079
+ google-play; f3ab
+ google-plus; f2b3
+ google-plus-g; f0d5
+ google-plus-square; f0d4
+ google-wallet; f1ee
+ gopuram; f664
+ graduation-cap; f19d
+ gratipay; f184
+ grav; f2d6
+ greater-than; f531
+ greater-than-equal; f532
+ grimace; f57f
+ grin; f580
+ grin-alt; f581
+ grin-beam; f582
+ grin-beam-sweat; f583
+ grin-hearts; f584
+ grin-squint; f585
+ grin-squint-tears; f586
+ grin-stars; f587
+ grin-tears; f588
+ grin-tongue; f589
+ grin-tongue-squint; f58a
+ grin-tongue-wink; f58b
+ grin-wink; f58c
+ grip-horizontal; f58d
+ grip-lines; f7a4
+ grip-lines-vertical; f7a5
+ grip-vertical; f58e
+ gripfire; f3ac
+ grunt; f3ad
+ guilded e07e
+ guitar; f7a6
+ gulp; f3ae
+ h-square; f0fd
+ hacker-news; f1d4
+ hacker-news-square; f3af
+ hackerrank; f5f7
+ hamburger; f805
+ hammer; f6e3
+ hamsa; f665
+ hand-holding; f4bd
+ hand-holding-heart; f4be
+ hand-holding-medical e05c
+ hand-holding-usd; f4c0
+ hand-holding-water; f4c1
+ hand-lizard; f258
+ hand-middle-finger; f806
+ hand-paper; f256
+ hand-peace; f25b
+ hand-point-down; f0a7
+ hand-point-left; f0a5
+ hand-point-right; f0a4
+ hand-point-up; f0a6
+ hand-pointer; f25a
+ hand-rock; f255
+ hand-scissors; f257
+ hand-sparkles e05d
+ hand-spock; f259
+ hands; f4c2
+ hands-helping; f4c4
+ hands-wash e05e
+ handshake; f2b5
+ handshake-alt-slash e05f
+ handshake-slash e060
+ hanukiah; f6e6
+ hard-hat; f807
+ hashtag; f292
+ hat-cowboy; f8c0
+ hat-cowboy-side; f8c1
+ hat-wizard; f6e8
+ hdd; f0a0
+ head-side-cough e061
+ head-side-cough-slash e062
+ head-side-mask e063
+ head-side-virus e064
+ heading; f1dc
+ headphones; f025
+ headphones-alt; f58f
+ headset; f590
+ heart; f004
+ heart-broken; f7a9
+ heartbeat; f21e
+ helicopter; f533
+ highlighter; f591
+ hiking; f6ec
+ hippo; f6ed
+ hips; f452
+ hire-a-helper; f3b0
+ history; f1da
+ hive e07f
+ hockey-puck; f453
+ holly-berry; f7aa
+ home; f015
+ hooli; f427
+ hornbill; f592
+ horse; f6f0
+ horse-head; f7ab
+ hospital; f0f8
+ hospital-alt; f47d
+ hospital-symbol; f47e
+ hospital-user; f80d
+ hot-tub; f593
+ hotdog; f80f
+ hotel; f594
+ hotjar; f3b1
+ hourglass; f254
+ hourglass-end; f253
+ hourglass-half; f252
+ hourglass-start; f251
+ house-damage; f6f1
+ house-user e065
+ houzz; f27c
+ hryvnia; f6f2
+ html5; f13b
+ hubspot; f3b2
+ i-cursor; f246
+ ice-cream; f810
+ icicles; f7ad
+ icons; f86d
+ id-badge; f2c1
+ id-card; f2c2
+ id-card-alt; f47f
+ ideal e013
+ igloo; f7ae
+ image; f03e
+ images; f302
+ imdb; f2d8
+ inbox; f01c
+ indent; f03c
+ industry; f275
+ infinity; f534
+ info; f129
+ info-circle; f05a
+ innosoft e080
+ instagram; f16d
+ instagram-square e055
+ instalod e081
+ intercom; f7af
+ internet-explorer; f26b
+ invision; f7b0
+ ioxhost; f208
+ italic; f033
+ itch-io; f83a
+ itunes; f3b4
+ itunes-note; f3b5
+ java; f4e4
+ jedi; f669
+ jedi-order; f50e
+ jenkins; f3b6
+ jira; f7b1
+ joget; f3b7
+ joint; f595
+ joomla; f1aa
+ journal-whills; f66a
+ js; f3b8
+ js-square; f3b9
+ jsfiddle; f1cc
+ kaaba; f66b
+ kaggle; f5fa
+ key; f084
+ keybase; f4f5
+ keyboard; f11c
+ keycdn; f3ba
+ khanda; f66d
+ kickstarter; f3bb
+ kickstarter-k; f3bc
+ kiss; f596
+ kiss-beam; f597
+ kiss-wink-heart; f598
+ kiwi-bird; f535
+ korvue; f42f
+ landmark; f66f
+ language; f1ab
+ laptop; f109
+ laptop-code; f5fc
+ laptop-house e066
+ laptop-medical; f812
+ laravel; f3bd
+ lastfm; f202
+ lastfm-square; f203
+ laugh; f599
+ laugh-beam; f59a
+ laugh-squint; f59b
+ laugh-wink; f59c
+ layer-group; f5fd
+ leaf; f06c
+ leanpub; f212
+ lemon; f094
+ less; f41d
+ less-than; f536
+ less-than-equal; f537
+ level-down-alt; f3be
+ level-up-alt; f3bf
+ life-ring; f1cd
+ lightbulb; f0eb
+ line; f3c0
+ link; f0c1
+ linkedin; f08c
+ linkedin-in; f0e1
+ linode; f2b8
+ linux; f17c
+ lira-sign; f195
+ list; f03a
+ list-alt; f022
+ list-ol; f0cb
+ list-ul; f0ca
+ location-arrow; f124
+ lock; f023
+ lock-open; f3c1
+ long-arrow-alt-down; f309
+ long-arrow-alt-left; f30a
+ long-arrow-alt-right; f30b
+ long-arrow-alt-up; f30c
+ low-vision; f2a8
+ luggage-cart; f59d
+ lungs; f604
+ lungs-virus e067
+ lyft; f3c3
+ magento; f3c4
+ magic; f0d0
+ magnet; f076
+ mail-bulk; f674
+ mailchimp; f59e
+ male; f183
+ mandalorian; f50f
+ map; f279
+ map-marked; f59f
+ map-marked-alt; f5a0
+ map-marker; f041
+ map-marker-alt; f3c5
+ map-pin; f276
+ map-signs; f277
+ markdown; f60f
+ marker; f5a1
+ mars; f222
+ mars-double; f227
+ mars-stroke; f229
+ mars-stroke-h; f22b
+ mars-stroke-v; f22a
+ mask; f6fa
+ mastodon; f4f6
+ maxcdn; f136
+ mdb; f8ca
+ medal; f5a2
+ medapps; f3c6
+ medium; f23a
+ medium-m; f3c7
+ medkit; f0fa
+ medrt; f3c8
+ meetup; f2e0
+ megaport; f5a3
+ meh; f11a
+ meh-blank; f5a4
+ meh-rolling-eyes; f5a5
+ memory; f538
+ mendeley; f7b3
+ menorah; f676
+ mercury; f223
+ meteor; f753
+ microblog e01a
+ microchip; f2db
+ microphone; f130
+ microphone-alt; f3c9
+ microphone-alt-slash; f539
+ microphone-slash; f131
+ microscope; f610
+ microsoft; f3ca
+ minus; f068
+ minus-circle; f056
+ minus-square; f146
+ mitten; f7b5
+ mix; f3cb
+ mixcloud; f289
+ mixer e056
+ mizuni; f3cc
+ mobile; f10b
+ mobile-alt; f3cd
+ modx; f285
+ monero; f3d0
+ money-bill; f0d6
+ money-bill-alt; f3d1
+ money-bill-wave; f53a
+ money-bill-wave-alt; f53b
+ money-check; f53c
+ money-check-alt; f53d
+ monument; f5a6
+ moon; f186
+ mortar-pestle; f5a7
+ mosque; f678
+ motorcycle; f21c
+ mountain; f6fc
+ mouse; f8cc
+ mouse-pointer; f245
+ mug-hot; f7b6
+ music; f001
+ napster; f3d2
+ neos; f612
+ network-wired; f6ff
+ neuter; f22c
+ newspaper; f1ea
+ nimblr; f5a8
+ node; f419
+ node-js; f3d3
+ not-equal; f53e
+ notes-medical; f481
+ npm; f3d4
+ ns8; f3d5
+ nutritionix; f3d6
+ object-group; f247
+ object-ungroup; f248
+ octopus-deploy e082
+ odnoklassniki; f263
+ odnoklassniki-square; f264
+ oil-can; f613
+ old-republic; f510
+ om; f679
+ opencart; f23d
+ openid; f19b
+ opera; f26a
+ optin-monster; f23c
+ orcid; f8d2
+ osi; f41a
+ otter; f700
+ outdent; f03b
+ page4; f3d7
+ pagelines; f18c
+ pager; f815
+ paint-brush; f1fc
+ paint-roller; f5aa
+ palette; f53f
+ palfed; f3d8
+ pallet; f482
+ paper-plane; f1d8
+ paperclip; f0c6
+ parachute-box; f4cd
+ paragraph; f1dd
+ parking; f540
+ passport; f5ab
+ pastafarianism; f67b
+ paste; f0ea
+ patreon; f3d9
+ pause; f04c
+ pause-circle; f28b
+ paw; f1b0
+ paypal; f1ed
+ peace; f67c
+ pen; f304
+ pen-alt; f305
+ pen-fancy; f5ac
+ pen-nib; f5ad
+ pen-square; f14b
+ pencil-alt; f303
+ pencil-ruler; f5ae
+ penny-arcade; f704
+ people-arrows e068
+ people-carry; f4ce
+ pepper-hot; f816
+ perbyte e083
+ percent; f295
+ percentage; f541
+ periscope; f3da
+ person-booth; f756
+ phabricator; f3db
+ phoenix-framework; f3dc
+ phoenix-squadron; f511
+ phone; f095
+ phone-alt; f879
+ phone-slash; f3dd
+ phone-square; f098
+ phone-square-alt; f87b
+ phone-volume; f2a0
+ photo-video; f87c
+ php; f457
+ pied-piper; f2ae
+ pied-piper-alt; f1a8
+ pied-piper-hat; f4e5
+ pied-piper-pp; f1a7
+ pied-piper-square e01e
+ piggy-bank; f4d3
+ pills; f484
+ pinterest; f0d2
+ pinterest-p; f231
+ pinterest-square; f0d3
+ pizza-slice; f818
+ place-of-worship; f67f
+ plane; f072
+ plane-arrival; f5af
+ plane-departure; f5b0
+ plane-slash e069
+ play; f04b
+ play-circle; f144
+ playstation; f3df
+ plug; f1e6
+ plus; f067
+ plus-circle; f055
+ plus-square; f0fe
+ podcast; f2ce
+ poll; f681
+ poll-h; f682
+ poo; f2fe
+ poo-storm; f75a
+ poop; f619
+ portrait; f3e0
+ pound-sign; f154
+ power-off; f011
+ pray; f683
+ praying-hands; f684
+ prescription; f5b1
+ prescription-bottle; f485
+ prescription-bottle-alt; f486
+ print; f02f
+ procedures; f487
+ product-hunt; f288
+ project-diagram; f542
+ pump-medical e06a
+ pump-soap e06b
+ pushed; f3e1
+ puzzle-piece; f12e
+ python; f3e2
+ qq; f1d6
+ qrcode; f029
+ question; f128
+ question-circle; f059
+ quidditch; f458
+ quinscape; f459
+ quora; f2c4
+ quote-left; f10d
+ quote-right; f10e
+ quran; f687
+ r-project; f4f7
+ radiation; f7b9
+ radiation-alt; f7ba
+ rainbow; f75b
+ random; f074
+ raspberry-pi; f7bb
+ ravelry; f2d9
+ react; f41b
+ reacteurope; f75d
+ readme; f4d5
+ rebel; f1d0
+ receipt; f543
+ record-vinyl; f8d9
+ recycle; f1b8
+ red-river; f3e3
+ reddit; f1a1
+ reddit-alien; f281
+ reddit-square; f1a2
+ redhat; f7bc
+ redo; f01e
+ redo-alt; f2f9
+ registered; f25d
+ remove-format; f87d
+ renren; f18b
+ reply; f3e5
+ reply-all; f122
+ replyd; f3e6
+ republican; f75e
+ researchgate; f4f8
+ resolving; f3e7
+ restroom; f7bd
+ retweet; f079
+ rev; f5b2
+ ribbon; f4d6
+ ring; f70b
+ road; f018
+ robot; f544
+ rocket; f135
+ rocketchat; f3e8
+ rockrms; f3e9
+ route; f4d7
+ rss; f09e
+ rss-square; f143
+ ruble-sign; f158
+ ruler; f545
+ ruler-combined; f546
+ ruler-horizontal; f547
+ ruler-vertical; f548
+ running; f70c
+ rupee-sign; f156
+ rust e07a
+ sad-cry; f5b3
+ sad-tear; f5b4
+ safari; f267
+ salesforce; f83b
+ sass; f41e
+ satellite; f7bf
+ satellite-dish; f7c0
+ save; f0c7
+ schlix; f3ea
+ school; f549
+ screwdriver; f54a
+ scribd; f28a
+ scroll; f70e
+ sd-card; f7c2
+ search; f002
+ search-dollar; f688
+ search-location; f689
+ search-minus; f010
+ search-plus; f00e
+ searchengin; f3eb
+ seedling; f4d8
+ sellcast; f2da
+ sellsy; f213
+ server; f233
+ servicestack; f3ec
+ shapes; f61f
+ share; f064
+ share-alt; f1e0
+ share-alt-square; f1e1
+ share-square; f14d
+ shekel-sign; f20b
+ shield-alt; f3ed
+ shield-virus e06c
+ ship; f21a
+ shipping-fast; f48b
+ shirtsinbulk; f214
+ shoe-prints; f54b
+ shopify e057
+ shopping-bag; f290
+ shopping-basket; f291
+ shopping-cart; f07a
+ shopware; f5b5
+ shower; f2cc
+ shuttle-van; f5b6
+ sign; f4d9
+ sign-in-alt; f2f6
+ sign-language; f2a7
+ sign-out-alt; f2f5
+ signal; f012
+ signature; f5b7
+ sim-card; f7c4
+ simplybuilt; f215
+ sink e06d
+ sistrix; f3ee
+ sitemap; f0e8
+ sith; f512
+ skating; f7c5
+ sketch; f7c6
+ skiing; f7c9
+ skiing-nordic; f7ca
+ skull; f54c
+ skull-crossbones; f714
+ skyatlas; f216
+ skype; f17e
+ slack; f198
+ slack-hash; f3ef
+ slash; f715
+ sleigh; f7cc
+ sliders-h; f1de
+ slideshare; f1e7
+ smile; f118
+ smile-beam; f5b8
+ smile-wink; f4da
+ smog; f75f
+ smoking; f48d
+ smoking-ban; f54d
+ sms; f7cd
+ snapchat; f2ab
+ snapchat-ghost; f2ac
+ snapchat-square; f2ad
+ snowboarding; f7ce
+ snowflake; f2dc
+ snowman; f7d0
+ snowplow; f7d2
+ soap e06e
+ socks; f696
+ solar-panel; f5ba
+ sort; f0dc
+ sort-alpha-down; f15d
+ sort-alpha-down-alt; f881
+ sort-alpha-up; f15e
+ sort-alpha-up-alt; f882
+ sort-amount-down; f160
+ sort-amount-down-alt; f884
+ sort-amount-up; f161
+ sort-amount-up-alt; f885
+ sort-down; f0dd
+ sort-numeric-down; f162
+ sort-numeric-down-alt; f886
+ sort-numeric-up; f163
+ sort-numeric-up-alt; f887
+ sort-up; f0de
+ soundcloud; f1be
+ sourcetree; f7d3
+ spa; f5bb
+ space-shuttle; f197
+ speakap; f3f3
+ speaker-deck; f83c
+ spell-check; f891
+ spider; f717
+ spinner; f110
+ splotch; f5bc
+ spotify; f1bc
+ spray-can; f5bd
+ square; f0c8
+ square-full; f45c
+ square-root-alt; f698
+ squarespace; f5be
+ stack-exchange; f18d
+ stack-overflow; f16c
+ stackpath; f842
+ stamp; f5bf
+ star; f005
+ star-and-crescent; f699
+ star-half; f089
+ star-half-alt; f5c0
+ star-of-david; f69a
+ star-of-life; f621
+ staylinked; f3f5
+ steam; f1b6
+ steam-square; f1b7
+ steam-symbol; f3f6
+ step-backward; f048
+ step-forward; f051
+ stethoscope; f0f1
+ sticker-mule; f3f7
+ sticky-note; f249
+ stop; f04d
+ stop-circle; f28d
+ stopwatch; f2f2
+ stopwatch-20 e06f
+ store; f54e
+ store-alt; f54f
+ store-alt-slash e070
+ store-slash e071
+ strava; f428
+ stream; f550
+ street-view; f21d
+ strikethrough; f0cc
+ stripe; f429
+ stripe-s; f42a
+ stroopwafel; f551
+ studiovinari; f3f8
+ stumbleupon; f1a4
+ stumbleupon-circle; f1a3
+ subscript; f12c
+ subway; f239
+ suitcase; f0f2
+ suitcase-rolling; f5c1
+ sun; f185
+ superpowers; f2dd
+ superscript; f12b
+ supple; f3f9
+ surprise; f5c2
+ suse; f7d6
+ swatchbook; f5c3
+ swift; f8e1
+ swimmer; f5c4
+ swimming-pool; f5c5
+ symfony; f83d
+ synagogue; f69b
+ sync; f021
+ sync-alt; f2f1
+ syringe; f48e
+ table; f0ce
+ table-tennis; f45d
+ tablet; f10a
+ tablet-alt; f3fa
+ tablets; f490
+ tachometer-alt; f3fd
+ tag; f02b
+ tags; f02c
+ tape; f4db
+ tasks; f0ae
+ taxi; f1ba
+ teamspeak; f4f9
+ teeth; f62e
+ teeth-open; f62f
+ telegram; f2c6
+ telegram-plane; f3fe
+ temperature-high; f769
+ temperature-low; f76b
+ tencent-weibo; f1d5
+ tenge; f7d7
+ terminal; f120
+ text-height; f034
+ text-width; f035
+ th; f00a
+ th-large; f009
+ th-list; f00b
+ the-red-yeti; f69d
+ theater-masks; f630
+ themeco; f5c6
+ themeisle; f2b2
+ thermometer; f491
+ thermometer-empty; f2cb
+ thermometer-full; f2c7
+ thermometer-half; f2c9
+ thermometer-quarter; f2ca
+ thermometer-three-quarters; f2c8
+ think-peaks; f731
+ thumbs-down; f165
+ thumbs-up; f164
+ thumbtack; f08d
+ ticket-alt; f3ff
+ tiktok e07b
+ times; f00d
+ times-circle; f057
+ tint; f043
+ tint-slash; f5c7
+ tired; f5c8
+ toggle-off; f204
+ toggle-on; f205
+ toilet; f7d8
+ toilet-paper; f71e
+ toilet-paper-slash e072
+ toolbox; f552
+ tools; f7d9
+ tooth; f5c9
+ torah; f6a0
+ torii-gate; f6a1
+ tractor; f722
+ trade-federation; f513
+ trademark; f25c
+ traffic-light; f637
+ trailer e041
+ train; f238
+ tram; f7da
+ transgender; f224
+ transgender-alt; f225
+ trash; f1f8
+ trash-alt; f2ed
+ trash-restore; f829
+ trash-restore-alt; f82a
+ tree; f1bb
+ trello; f181
+ trophy; f091
+ truck; f0d1
+ truck-loading; f4de
+ truck-monster; f63b
+ truck-moving; f4df
+ truck-pickup; f63c
+ tshirt; f553
+ tty; f1e4
+ tumblr; f173
+ tumblr-square; f174
+ tv; f26c
+ twitch; f1e8
+ twitter; f099
+ twitter-square; f081
+ typo3; f42b
+ uber; f402
+ ubuntu; f7df
+ uikit; f403
+ umbraco; f8e8
+ umbrella; f0e9
+ umbrella-beach; f5ca
+ uncharted e084
+ underline; f0cd
+ undo; f0e2
+ undo-alt; f2ea
+ uniregistry; f404
+ unity e049
+ universal-access; f29a
+ university; f19c
+ unlink; f127
+ unlock; f09c
+ unlock-alt; f13e
+ unsplash e07c
+ untappd; f405
+ upload; f093
+ ups; f7e0
+ usb; f287
+ user; f007
+ user-alt; f406
+ user-alt-slash; f4fa
+ user-astronaut; f4fb
+ user-check; f4fc
+ user-circle; f2bd
+ user-clock; f4fd
+ user-cog; f4fe
+ user-edit; f4ff
+ user-friends; f500
+ user-graduate; f501
+ user-injured; f728
+ user-lock; f502
+ user-md; f0f0
+ user-minus; f503
+ user-ninja; f504
+ user-nurse; f82f
+ user-plus; f234
+ user-secret; f21b
+ user-shield; f505
+ user-slash; f506
+ user-tag; f507
+ user-tie; f508
+ user-times; f235
+ users; f0c0
+ users-cog; f509
+ users-slash e073
+ usps; f7e1
+ ussunnah; f407
+ utensil-spoon; f2e5
+ utensils; f2e7
+ vaadin; f408
+ vector-square; f5cb
+ venus; f221
+ venus-double; f226
+ venus-mars; f228
+ vest e085
+ vest-patches e086
+ viacoin; f237
+ viadeo; f2a9
+ viadeo-square; f2aa
+ vial; f492
+ vials; f493
+ viber; f409
+ video; f03d
+ video-slash; f4e2
+ vihara; f6a7
+ vimeo; f40a
+ vimeo-square; f194
+ vimeo-v; f27d
+ vine; f1ca
+ virus e074
+ virus-slash e075
+ viruses e076
+ vk; f189
+ vnv; f40b
+ voicemail; f897
+ volleyball-ball; f45f
+ volume-down; f027
+ volume-mute; f6a9
+ volume-off; f026
+ volume-up; f028
+ vote-yea; f772
+ vr-cardboard; f729
+ vuejs; f41f
+ walking; f554
+ wallet; f555
+ warehouse; f494
+ watchman-monitoring e087
+ water; f773
+ wave-square; f83e
+ waze; f83f
+ weebly; f5cc
+ weibo; f18a
+ weight; f496
+ weight-hanging; f5cd
+ weixin; f1d7
+ whatsapp; f232
+ whatsapp-square; f40c
+ wheelchair; f193
+ whmcs; f40d
+ wifi; f1eb
+ wikipedia-w; f266
+ wind; f72e
+ window-close; f410
+ window-maximize; f2d0
+ window-minimize; f2d1
+ window-restore; f2d2
+ windows; f17a
+ wine-bottle; f72f
+ wine-glass; f4e3
+ wine-glass-alt; f5ce
+ wix; f5cf
+ wizards-of-the-coast; f730
+ wodu e088
+ wolf-pack-battalion; f514
+ won-sign; f159
+ wordpress; f19a
+ wordpress-simple; f411
+ wpbeginner; f297
+ wpexplorer; f2de
+ wpforms; f298
+ wpressr; f3e4
+ wrench; f0ad
+ x-ray; f497
+ xbox; f412
+ xing; f168
+ xing-square; f169
+ y-combinator; f23b
+ yahoo; f19e
+ yammer; f840
+ yandex; f413
+ yandex-international; f414
+ yarn; f7e3
+ yelp; f1e9
+ yen-sign; f157
+ yin-yang; f6ad
+ yoast; f2b1
+ youtube; f167
+ youtube-square; f431
+ zhihu; f63f
diff --git a/default/.local/share/thesiah/icons/TheSiahxyz-git.png b/default/.local/share/thesiah/icons/TheSiahxyz-git.png
new file mode 100644
index 0000000..abf2bd4
--- /dev/null
+++ b/default/.local/share/thesiah/icons/TheSiahxyz-git.png
Binary files differ
diff --git a/default/.local/share/thesiah/icons/TheSiahxyz.png b/default/.local/share/thesiah/icons/TheSiahxyz.png
new file mode 100644
index 0000000..4b0c9ae
--- /dev/null
+++ b/default/.local/share/thesiah/icons/TheSiahxyz.png
Binary files differ
diff --git a/default/.local/share/thesiah/icons/TheSiahxyz.webp b/default/.local/share/thesiah/icons/TheSiahxyz.webp
new file mode 100644
index 0000000..e49ca9f
--- /dev/null
+++ b/default/.local/share/thesiah/icons/TheSiahxyz.webp
Binary files differ
diff --git a/default/.local/share/thesiah/icons/TheSiahxyz.xcf b/default/.local/share/thesiah/icons/TheSiahxyz.xcf
new file mode 100644
index 0000000..2016dfd
--- /dev/null
+++ b/default/.local/share/thesiah/icons/TheSiahxyz.xcf
Binary files differ
diff --git a/default/.local/share/thesiah/keys/calcurse b/default/.local/share/thesiah/keys/calcurse
new file mode 100644
index 0000000..a20e624
--- /dev/null
+++ b/default/.local/share/thesiah/keys/calcurse
@@ -0,0 +1,10 @@
+ _
+ ___ __ _| | ___ _ _ _ __ ___ ___
+ / __/ _` | |/ __| | | | '__/ __|/ _ \
+| (_| (_| | | (__| |_| | | \__ \ __/
+ \___\__,_|_|\___|\__,_|_| |___/\___|
+
+calcurse is the calendar and schedule manager.
+ tab - Switch from calendar to todo to appointments
+ h/j/k/l - Move left/down/up/right
+ Most other bindings are listed in the program.
diff --git a/default/.local/share/thesiah/keys/mutt b/default/.local/share/thesiah/keys/mutt
new file mode 100644
index 0000000..41069ff
--- /dev/null
+++ b/default/.local/share/thesiah/keys/mutt
@@ -0,0 +1,34 @@
+ _ _
+ _ __ ___ _ _| |_| |_
+| '_ ` _ \| | | | __| __|
+| | | | | | |_| | |_| |_
+|_| |_| |_|\__,_|\__|\__|
+
+mutt is the email client.
+ j/k - Move down/up
+ d/u - Move down/up half page
+ gg - Move to top
+ v - View/download attachments
+ G - Move to last message
+ r - Reply
+ R - Reply all
+ S - Sync/save mailbox changes
+ D - Mark message for deletion
+ U - Unmark message for deletion
+ ctrl-u - Seek urls
+ ,, - Seek urls
+ ctrl-f - Search mail indexed with notmuch
+ ctrl-r - Mark all as read
+ l - Limit mail
+ o - Run quick sync with offlineimap
+ O - Run full sync with offlineimap
+ C - Copy a message to another mailbox
+ M - Move a message to another mailbox
+ B - Hide/reveal sidebar
+ ctrl-j/k - Move down/up on sidebar
+ ctrl-o - Open box selected in sidebar
+ gi - Go to inbox
+ gs - Go to sent mail
+ gd - Go to drafts
+ gS - Go to spam
+ i# - Go to a different account (# is the number of the account)
diff --git a/default/.local/share/thesiah/keys/ncmpcpp b/default/.local/share/thesiah/keys/ncmpcpp
new file mode 100644
index 0000000..75cdf26
--- /dev/null
+++ b/default/.local/share/thesiah/keys/ncmpcpp
@@ -0,0 +1,21 @@
+
+ _ __ ___ _ __ ___ _ __ ___ _ __ _ __
+| '_ \ / __| '_ ` _ \| '_ \ / __| '_ \| '_ \
+| | | | (__| | | | | | |_) | (__| |_) | |_) |
+|_| |_|\___|_| |_| |_| .__/ \___| .__/| .__/
+ |_| |_| |_|
+
+ncmpcpp is the music player.
+ h/j/k/l - Move left/down/up/right
+ d/u - Down/up page
+ a - Add song(s) to playlist
+ c - Clear playlist
+ g - Go to top
+ G - Go to bottom
+ p - Pause
+ m - Media library
+ f - Music sorted by directory structure
+ t - Tag editor
+ s - Search
+ v - Visualizer
+ P - Playlist
diff --git a/default/.local/share/thesiah/keys/newsboat b/default/.local/share/thesiah/keys/newsboat
new file mode 100644
index 0000000..b6953b3
--- /dev/null
+++ b/default/.local/share/thesiah/keys/newsboat
@@ -0,0 +1,22 @@
+ _ _
+ _ __ _____ _____| |__ ___ __ _| |_
+| '_ \ / _ \ \ /\ / / __| '_ \ / _ \ / _` | __|
+| | | | __/\ V V /\__ \ |_) | (_) | (_| | |_
+|_| |_|\___| \_/\_/ |___/_.__/ \___/ \__,_|\__|
+
+newsboat is the RSS reader.
+ j/k - Move down/up
+ l - Open entry
+ h/q - Back/quit
+ Q - Quit immediately
+ J/K - Previous/next feed
+ n - Next unread
+ N - Previous unread
+ a - Toggle article read/unread
+ A - Mark all as read
+ U - Show all URLs
+ ,, - Open main link with linkhandler
+ ,p - Pick which program to open link with
+ ,v - Open video link in mpv
+ ,w - Open link in w3m
+ ,c - Copy link to clipboard
diff --git a/default/.local/share/thesiah/keys/nsxiv b/default/.local/share/thesiah/keys/nsxiv
new file mode 100644
index 0000000..78403bb
--- /dev/null
+++ b/default/.local/share/thesiah/keys/nsxiv
@@ -0,0 +1,16 @@
+
+ _ __ _____ _(_)_ __
+| '_ \/ __\ \/ / \ \ / /
+| | | \__ \> <| |\ V /
+|_| |_|___/_/\_\_| \_/
+
+nsxiv is the image viewer.
+ h/j/k/l - Pan image
+ -/+ - Zoom out/in
+ Enter - Toggle thumbnail mode
+ f - Fullscreen
+ n/p - Previous/next image in list/directory
+ r - Reload image if changed
+ m - Mark/unmark image
+ w - Zoom to fit window
+ ctrl-x - Run external command (see ~/.config/nsxiv/exec/key-handler for options)
diff --git a/default/.local/share/thesiah/keys/zathura b/default/.local/share/thesiah/keys/zathura
new file mode 100644
index 0000000..8fa1ec8
--- /dev/null
+++ b/default/.local/share/thesiah/keys/zathura
@@ -0,0 +1,19 @@
+ _ _
+ ______ _| |_| |__ _ _ _ __ __ _
+|_ / _` | __| '_ \| | | | '__/ _` |
+ / / (_| | |_| | | | |_| | | | (_| |
+/___\__,_|\__|_| |_|\__,_|_| \__,_|
+
+zathura is the pdf/djvu reader.
+ h/j/k/l - Move left/down/up/right in document
+ d/u - Down/up a half page
+ gg - Top of document
+ G - Bottom of document
+ f - Highlight URLS to follow
+ J/K - Zoom out/in
+ s - Zoom to fit width
+ a - Zoom to fit height
+ r - Reload document if changed
+ R - Rotate document
+ D - Toggle dual-page mode
+ p - Print document
diff --git a/default/.local/share/thesiah/snippets b/default/.local/share/thesiah/snippets
new file mode 100644
index 0000000..2965b7f
--- /dev/null
+++ b/default/.local/share/thesiah/snippets
@@ -0,0 +1,6 @@
+# TheSiahxyz's URLs
+https://thesiah.xyz "Sever TheSiahxyz"
+https://git.thesiah.xyz "Git TheSiahxyz"
+https://github.com/TheSiahxyz?tab=repositories "Github TheSiahxyz"
+https://mail.thesiah.xyz "Mail TheSiahxyz"
+https://nextcloud.thesiah.xyz "Nextcloud TheSiahxyz"
diff --git a/default/.local/share/thesiah/ttymaps.kmap b/default/.local/share/thesiah/ttymaps.kmap
new file mode 100644
index 0000000..7d7e450
--- /dev/null
+++ b/default/.local/share/thesiah/ttymaps.kmap
@@ -0,0 +1,3 @@
+keymaps 0-2,4-6,8-9,12
+keycode 29 = Caps_Lock
+keycode 58 = Control
diff --git a/default/.local/share/venvs/default-requirements.txt b/default/.local/share/venvs/default-requirements.txt
new file mode 100644
index 0000000..a92c5ed
--- /dev/null
+++ b/default/.local/share/venvs/default-requirements.txt
@@ -0,0 +1,25 @@
+cairosvg
+ipykernel
+ipython
+jupyter
+jupyter_client
+jupyter_core
+jupytext
+kaleido
+nbformat
+notebook
+pillow
+pip
+pipx
+plotly
+pnglatex
+pycairo
+pylatexenc
+pynvim
+pyperclip
+requests
+stig
+trash-cli
+urlscan
+websocket-client
+wheel
diff --git a/default/.ssh/config b/default/.ssh/config
new file mode 100644
index 0000000..4be131d
--- /dev/null
+++ b/default/.ssh/config
@@ -0,0 +1 @@
+Match host * exec "gpg-connect-agent UPDATESTARTUPTTY /bye"
diff --git a/default/Music/.music.txt b/default/Music/.music.txt
new file mode 100644
index 0000000..b32b089
--- /dev/null
+++ b/default/Music/.music.txt
@@ -0,0 +1,899 @@
+youtube 4GCXXyhOK5A
+youtube gTilnkNnoww
+youtube 6wSZk6a1GGQ
+youtube 8Ceit6F0a5s
+youtube yRVotpLaCD4
+youtube J7p4bzqLvCw
+youtube RmYCOm4ehKs
+youtube fdrjPRnTYC0
+youtube AE9kr-3lQFo
+youtube hI106kKGBOI
+youtube Bv1Wido-lJY
+youtube py8v0sIXlZY
+youtube XeskZsa7GbI
+youtube 3gv-XKHj0rw
+youtube pkUc8CcTXdQ
+youtube Nv3bQao8l3I
+youtube Z_4uvlzSlgE
+youtube 59DAuFD9D0U
+youtube 7B6zArUXVbE
+youtube ggNQZAahQog
+youtube TSGOZyt2iDI
+youtube 3NNhrqHZqlI
+youtube 63Wt2GFXzjY
+youtube TsulQZDKM-g
+youtube xg_Y7Or_hWM
+youtube FXfvbMEWkhk
+youtube UC8JSS6O7OU
+youtube vHhK6jhzj1c
+youtube ycnS_1a56CM
+youtube XTeOZbnw5U8
+youtube uwLltXRl3Ew
+youtube y6xICit9y9Q
+youtube SqK_9tpt_SY
+youtube bu3kc9KeRV0
+youtube oFiN9--6Ggw
+youtube -AdptiRTx6o
+youtube Kcj5-jWxgKs
+youtube pXteM6_FdlQ
+youtube HiXx5JFRxb4
+youtube JtN3eRbSHEM
+youtube IDyQO0QuvKY
+youtube qBjuKIadfxs
+youtube iorkZ_eylSY
+youtube 7OEeWmQuEMg
+youtube ii-2fYaxd_0
+youtube yoqeXpw3ne8
+youtube yjBbOn-g5bw
+youtube vTMAa6zZ7jY
+youtube 2Kiob5f9A1g
+youtube bqfhHsrmVZI
+youtube lAJazaLrHfQ
+youtube hi2N3fsYIqs
+youtube xl8thVrlvjI
+youtube w7jKJHyX1z8
+youtube 9ssQKlLxBdQ
+youtube WmQHSkjgyDM
+youtube I203G1sMGDg
+youtube KrYrvycI4mo
+youtube 1uBPOu3si5w
+youtube l4WhsXAvZgc
+youtube LSuTlGfl1y8
+youtube _02Y1ulz0Hk
+youtube _89SAQOjCEE
+youtube Yog_o7ZRa_M
+youtube 6li-ChcE37A
+youtube GO7XyHdYE0s
+youtube eS4Xbayh2jA
+youtube mtZVhT4o37Q
+youtube 9waVv2V6DNc
+youtube 8tdVIe5j7MI
+youtube ZvhBas3Bd-g
+youtube WDejpOn5BKg
+youtube JLBcAP7CxWo
+youtube J548BrZdi1w
+youtube fe2nKdL5gMg
+youtube YADvTXStr3E
+youtube c7v3OZ0JuGs
+youtube 0buawIg5F84
+youtube btLZ75-61jQ
+youtube 3foe3_8yEB4
+youtube lXMPoEj_ZJc
+youtube RjkTzXMwJms
+youtube TQIzIAJmmlQ
+youtube z7jrnhaOk7g
+youtube sZ2niy6wIls
+youtube uvjCEn1i7Qo
+youtube 3wfXJPj8YGw
+youtube BmT7RA-kvB4
+youtube DdR8JmIKqus
+youtube m9kStPfjnhQ
+youtube 0q6DR6EiPPo
+youtube 7YhH7-9wChI
+youtube s44D0IRdNOk
+youtube anQms0R9z9Q
+youtube U-WoYD6nHmk
+youtube LiFM_W5za-A
+youtube kRrs0qdzKA4
+youtube 43GvYuANOAw
+youtube 3adSHfY_GSE
+youtube zmJlOMvw5RY
+youtube m98v1Wm8SeA
+youtube AwVoO2_3xuU
+youtube c_EjoWowAT4
+youtube 4FyN_9fxFMk
+youtube deVMdsAWuVA
+youtube Vvwc-M9qbfU
+youtube 4uZGtL4qgdI
+youtube Ydc_iAzHatw
+youtube ZwUqWNDACzg
+youtube dGVOUZ634F8
+youtube XI4-dGlhsKE
+youtube BGESqfpN1J8
+youtube qYRw0ik7gLs
+youtube nLDVhW7mVZk
+youtube _ttwtumExG8
+youtube F5bzfi03xqg
+youtube r-vcN6-uBmk
+youtube TmyMV2Nj42Q
+youtube pX_Nv6gG6SY
+youtube ileD28ky7Dw
+youtube j8mbW0jW-T0
+youtube d2rbm-ESp1A
+youtube jgbLdMmBIPU
+youtube S2mfI5go0M0
+youtube MfZWh2bYYeE
+youtube R_MJyyDp-30
+youtube ULfAaYIuldI
+youtube PFZ0lw8pNy4
+youtube dmydp51589s
+youtube -3WQ75whElk
+youtube Er9jLJQ31TY
+youtube 8nm8fg3iFuw
+youtube ZxQkodR8Utc
+youtube 9hxOPcjg5qQ
+youtube ZGbBfaLrszU
+youtube Qf5Cj3AVLeY
+youtube gNwEaQg4JNI
+youtube lHZbJz9dpCI
+youtube OGw-f0coX6E
+youtube qqoHyTAFIPs
+youtube G5hbOrbfSRY
+youtube eAoTw0xBtM0
+youtube LJO0opJKAd0
+youtube 2cNM4FGn9yk
+youtube 0UQt0STXrK8
+youtube m4LGuu5b57c
+youtube VO88htcm09E
+youtube PUsqVlfOulc
+youtube 2x3Vlt-c4zo
+youtube 77BE9NahcLc
+youtube vMAe4gqL7QU
+youtube gAz5CB9UXI8
+youtube RwaOrpQMHss
+youtube frqZJd8w2NM
+youtube jdNceSXJTZA
+youtube 54vxYZKanOQ
+youtube ue30EjqYUqU
+youtube 4wuZsWr4kK4
+youtube 5oU-SkWXa4A
+youtube WK6NbKeu5lE
+youtube FcfCWcZHDD8
+youtube Z75rOowUTxk
+youtube ZDoH5dQ58ps
+youtube 8ykMyNHAdKk
+youtube iOKRYIMhaDk
+youtube ybiINVA_gLc
+youtube 1MnzUWjBQog
+youtube EQzqq0LNlto
+youtube 5CXTwUrotf4
+youtube cvQQEs4TDGM
+youtube zMxU7CJ_scA
+youtube B1P1Rj_ZZlc
+youtube XmA8aSA_EHQ
+youtube LrKZjDr9mtw
+youtube NwP6Lqmldsc
+youtube S-NomVRZKfU
+youtube PCDmd496TvQ
+youtube TetVMbwaurk
+youtube L1X8030K2gE
+youtube Pbs_3mSbQ7Y
+youtube QogFQ59raOs
+youtube YvfymsA9OIc
+youtube zFpoaELOrTo
+youtube SX_ViT4Ra7k
+youtube -tKVN2mAKRI
+youtube TQ8WlA2GXbk
+youtube ZRtdQ81jPUQ
+youtube ony539T074w
+youtube iuJDhFRDx9M
+youtube 1tk1pqwrOys
+youtube dy90tA3TT1c
+youtube -EKxzId_Sj4
+youtube 0xSiBpUdW4E
+youtube Dx_fKPBPYUI
+youtube Qp3b-RXtz4w
+youtube QW28YKqdxe0
+youtube 4DxL6IKmXx4
+youtube PDSkFeMVNFs
+youtube dlFA0Zq1k2A
+youtube 9aJVr5tTTWk
+youtube 2pECnr5MNuU
+youtube x8VYWazR5mE
+youtube wfCcs0vLysk
+youtube sr--GVIoluU
+youtube gJX2iy6nhHc
+youtube by4SYYWlhEs
+youtube jhOVibLEDhA
+youtube T0valuAksuo
+youtube dFf4AgBNR1E
+youtube Mi9uNu35Gmk
+youtube iqEr3P78fz8
+youtube VyKLQXOj0ts
+youtube UFQEttrn6CQ
+youtube gsT6eKsnT0M
+youtube -VKIqrvVOpo
+youtube tLQLa6lM3Us
+youtube Hh9yZWeTmVM
+youtube LIlZCmETvsY
+youtube 0YF8vecQWYs
+youtube 8OZDgBmehbA
+youtube 7940nuwCEYA
+youtube kagoEGKHZvU
+youtube YnSW8ian29w
+youtube ptnYBctoexk
+youtube bt8wNQJaKAk
+youtube yzC4hFK5P3g
+youtube m9SMT5ipbxk
+youtube WIKqgE4BwAY
+youtube a2GujJZfXpg
+youtube DuMqFknYHBs
+youtube Uh6dkL1M9DM
+youtube lzAyrgSqeeE
+youtube 3zh9Wb1KuW8
+youtube J5Z7tIq7bco
+youtube lkHlnWFnA0c
+youtube jJzw1h5CR-I
+youtube 4-Gw0TAM6-Q
+youtube O_DLtVuiqhI
+youtube uMeR2W19wT0
+youtube 51CH3dPaWXc
+youtube Y4nEEZwckuU
+youtube KTZ-y85Erus
+youtube XSkpuDseenY
+youtube yOAwvRmVIyo
+youtube C-o8pTi6vd8
+youtube CID-sYQNCew
+youtube qag4ewos4TE
+youtube 9MjAJSoaoSo
+youtube sOiMD45QGLs
+youtube pgXpM4l_MwI
+youtube kzZ6KXDM1RI
+youtube Rp0Z9YVOnKQ
+youtube 1FliVTcX8bQ
+youtube zkNzxsaCunU
+youtube K_xTet06SUo
+youtube gU5oN0KVofU
+youtube uKxyLmbOc0Q
+youtube M2cckDmNLMI
+youtube H08YWE4CIFQ
+youtube F64yFFnZfkI
+youtube hkBbUf4oGfA
+youtube mLW35YMzELE
+youtube m34DPnRUfMU
+youtube -kgOFJG881I
+youtube mp2-w15SXms
+youtube DC6JppqHkaM
+youtube O1bhZgkC4Gw
+youtube eq8r1ZTma08
+youtube PCp2iXA1uLE
+youtube EHw005ZqCXk
+youtube mJ1N7-HyH1A
+youtube 1_lap6dzSUc
+youtube j1hft9Wjq9U
+youtube s582L3gujnw
+youtube WJzSBLCaKc8
+youtube -zQWavER7to
+youtube hN5MBlGv2Ac
+youtube MpYy6wwqxoo
+youtube VEe_yIbW64w
+youtube NMA_isZYsYQ
+youtube GJI4Gv7NbmE
+youtube nROvY9uiYYk
+youtube yvHfl1JEPc0
+youtube Woorod1gJ_w
+youtube dHXC_ahjtEE
+youtube 6YZlFdTIdzM
+youtube j7CDb610Bg0
+youtube pS5d77DQHOI
+youtube 9lVPAWLWtWc
+youtube K4xLi8IF1FM
+youtube pmanD_s7G3U
+youtube 37W7Y2RRyiM
+youtube xGbxsiBZGPI
+youtube WPl10ZrhCtk
+youtube 5GUaMOpfmr8
+youtube eg65SbqmT0s
+youtube IV7usfiEbms
+youtube KId6eunoiWk
+youtube ItjnF7Q9IrU
+youtube gt-v_YCkaMY
+youtube a6QT0acJFQE
+youtube TcLLpZBWsck
+youtube 8iuLXODzL04
+youtube WWB01IuMvzA
+youtube yXZd7xVdpJ0
+youtube EQ94zflNqn4
+youtube UgS7vgquBvo
+youtube CbH2F0kXgTY
+youtube M6gcoDN9jBc
+youtube A_1t2Dkd2Io
+youtube Lxr9tvYUHcg
+youtube GVFR9zmQjec
+youtube qLMLzBYTTKQ
+youtube clU8c2fpk2s
+youtube SII-S-zCg-c
+youtube 1s84rIhPuhk
+youtube JfgOjtq440o
+youtube dFlDRhvM4L0
+youtube UM9XNpgrqVk
+youtube pfGI91CFtRg
+youtube Eze6-eHmtJg
+youtube U0TXIXTzJEY
+youtube 3eytpBOkOFA
+youtube sAuEeM_6zpk
+youtube 9qRCARM_LfE
+youtube rSD7jb4Xjq8
+youtube hm1na9R2uYA
+youtube kzdJkT4kp-A
+youtube IVbY5edMfCA
+youtube 7zBeQezaz4U
+youtube CwkzK-F0Y00
+youtube qNrRnnG8glY
+youtube jOegTv3a2h4
+youtube qpi9YXaChHI
+youtube NyUTYwZe_l4
+youtube Ae6gQmhaMn4
+youtube o1sUaVJUeB0
+youtube OIBODIPC_8Y
+youtube fhzKLBZJC3w
+youtube Gbz2C2gQREI
+youtube xefpHEg5UIA
+youtube BEULybZnLO8
+youtube Wxsnr7knQqI
+youtube T8y_RsF4TSw
+youtube x2rvSf0STBM
+youtube WPH1BLHKOJE
+youtube QAwG54HlFK8
+youtube Fqey8LxQxFU
+youtube MjXeOAouF3w
+youtube 3Oy7f-aBvFI
+youtube 214Z2HoETWg
+youtube gr3YgLuA9fk
+youtube kht6sTv_r-E
+youtube gU3ubk8u7dA
+youtube ogoIxkPjRts
+youtube p3tH4Ksu50o
+youtube Qpsjwxek0ds
+youtube J36z7AnhvOM
+youtube 9haQ9hcaoJY
+youtube 3C5RAeIRjsU
+youtube 1uztRCOFYyc
+youtube x0ebeKFwEkY
+youtube XGaaVjG_eUA
+youtube 5e1Ti6-DKDk
+youtube SuFScoO4tb0
+youtube C4COMmu2fxw
+youtube zRj_ZvsNek4
+youtube 3rXZW5mt5Ls
+youtube NKGVQRCeIgE
+youtube Ww8oxgqDQSs
+youtube 0xSiBpUdW4E
+youtube LB2rYW-IHK8
+youtube mHWzSGjnKaE
+youtube v6qlOszBN7M
+youtube mOZqd0bDDcQ
+youtube bqG_Ckci5l0
+youtube COcuU8LKawk
+youtube DdOeKuNTj4o
+youtube EK18vAp9VVc
+youtube wrmyqKRGW-0
+youtube vYw6-1znJ8o
+youtube KBKzIDdAO1s
+youtube xR-M7mfph_I
+youtube GpIXMCcrZBg
+youtube Fl3ZEiZu--s
+youtube k0YihgysbVs
+youtube 5IlAS7VzAbI
+youtube i6rXYiwuJwM
+youtube R89tf2GbxTM
+youtube DDWKuo3gXMQ
+youtube a1IuJLebHgM
+youtube WGCnKSW8abk
+youtube GxldQ9eX2wo
+youtube mgMPKPVXi_A
+youtube w46luJ4rUqw
+youtube zKyUMJemQHc
+youtube 9UNpL4MT6bY
+youtube HRXjP4HIoHc
+youtube UTgx2ln4qaU
+youtube snAKuQQeXWw
+youtube UPovD3kThbQ
+youtube -kcZNuFYnns
+youtube Kdl3erX8kA4
+youtube q-I62ICVEeE
+youtube H6qC2tMkL00
+youtube Yc4gp6oeN7k
+youtube 6Bs-YqjlRf0
+youtube xmXHQUAWLA8
+youtube UEajTd306bg
+youtube M53SNnh1ImA
+youtube e7u2aPzWmU4
+youtube sXft4UC8gkg
+youtube UMnJ6arAbZg
+youtube wJhWwt1OmT8
+youtube xOxlgvJ94PY
+youtube GWMjtmYtmDs
+youtube rj5wZqReXQE
+youtube qmSOA2sPHUU
+youtube csdBrz_544U
+youtube NbKH4iZqq1Y
+youtube HFtn3y77Vs0
+youtube kJEPeKPtZyw
+youtube IdQILQ6n1sk
+youtube rhIINpUvrCY
+youtube NfuVIBbCCnQ
+youtube amQZKaeEyv0
+youtube ZeIGVnkYX04
+youtube c_mVwBCLBCE
+youtube TEy5DPvyDSo
+youtube qNxyTO9SqlM
+youtube 6aP_zJ21Jmw
+youtube B2Nq47QZpuM
+youtube q-4JZVKB9I8
+youtube uMqMsRk5a5E
+youtube 7HgJIAUtICU
+youtube St7zJORL0gs
+youtube IUZMSDQKY4w
+youtube X59TlszGtfM
+youtube Dk8O9AHJKQs
+youtube UFWPJxH7kxI
+youtube ybD4IO7YPVM
+youtube BdL5ef3u8gQ
+youtube TuTrz1R0QBY
+youtube fgZnmfBFqgs
+youtube sHcf3dBKFaY
+youtube XYzZauasszI
+youtube hoMkmMILVFI
+youtube _HqgEyfSaTI
+youtube chPE-m-_x9E
+youtube GV_HP7yoIyc
+youtube d2Svr6j8OuI
+youtube Ei9XPysjEM4
+youtube Na_sJ7XtDl0
+youtube G8YNFjruzPk
+youtube yB5dzsWvyQI
+youtube ydc3bY-NgOg
+youtube dVXh4nzjxRc
+youtube xMj9RXsmiMg
+youtube wwp6d37uz70
+youtube ozPtL1R6mZ8
+youtube vK48ItIas-Y
+youtube IIetmfPAyiM
+youtube _d5VFDsviHE
+youtube aTiuJGBmR30
+youtube J-_ewuhSQMc
+youtube bCQ1kneXQv4
+youtube i1QZO9EiVmY
+youtube p2JUA8cYGS4
+youtube UbxtCV8nXnA
+youtube pyiOWNMfIMw
+youtube SPaNJD8qPyA
+youtube FXppROCqu94
+youtube kNDbaYEp0tU
+youtube qT_y5Yc8jSA
+youtube EJAEzPwcQPs
+youtube I8mKdGUI_eo
+youtube 0omCKIrbz48
+youtube Oa-Vc2amuA0
+youtube 7PIgSFJWM6s
+youtube 5sACmuL6eCw
+youtube X0PUdt1gt5c
+youtube FgAMCLzLE9c
+youtube 3SO4e6Yry4g
+youtube nDX3Hn_Pdbs
+youtube NGDi89A5FcU
+youtube xtFAmapbTbY
+youtube Hp9Z5TP1WfA
+youtube xiuOSWPsIyM
+youtube kWkRqhRGzUo
+youtube zRBcYVY5A2Y
+youtube SvtSWhyw7vs
+youtube iTxcnLTNLW8
+youtube tty54CzXZDg
+youtube EIHEsl4w5Oc
+youtube Go0ICyea0n4
+youtube VRAU3CtiRq8
+youtube 6gDhsUWCHrg
+youtube KHbg4y6EiQE
+youtube 387J4aGqFCs
+youtube IIcghg8XD9Y
+youtube eEjDZVe4NHY
+youtube 65xX7zyr-fA
+youtube vBJVWOOsB4A
+youtube cb6RNAkQU5g
+youtube 68RryFCt2Jk
+youtube M7Es4vCrd7Y
+youtube 8qGy6GvIcek
+youtube BW37E7kCyMI
+youtube Uhwy8vruGhA
+youtube AmagoooFlIQ
+youtube zQMV0dETHaw
+youtube U9-f4Mosb_A
+youtube YfB5xeC6VsE
+youtube QTJIKwZS-HE
+youtube viiL1_gEiMY
+youtube hmJCg_fet4w
+youtube P33-7vi0UL0
+youtube WX0Sd7KAoYw
+youtube axXjEXltccs
+youtube UvFDlL9Pwt4
+youtube TyCYGCCD6iQ
+youtube U31mUH2DnWk
+youtube C5PMgq5uNlg
+youtube WvIaKOaolxg
+youtube BBj3SCImk_A
+youtube lR14buHEdMY
+youtube zOkIe3RcTCs
+youtube yuTesDwv6uA
+youtube j-seAiANLG8
+youtube XuTIkXPCKQY
+youtube -E8rHkAlHmQ
+youtube sfJU0q1eG6Q
+youtube 3ZmIY-vuqsM
+youtube 9OUurVdRGsc
+youtube Wbb7fWQjz4c
+youtube nujn6wbr-e8
+youtube 92nsOGmP0gg
+youtube CyFrK4tL-SM
+youtube UTOxChG5Ucc
+youtube eUfOXJ2rXBU
+youtube Dmy0CkieM6s
+youtube 8PhC75qPXpE
+youtube CLNQ_qWdEeA
+youtube h0ccMK0U9nY
+youtube 5TgTPH2b6nM
+youtube JvQhJTM9OJU
+youtube pRosoqfxJY4
+youtube vMrI9LXohYo
+youtube iTPkkKRKGrQ
+youtube iJRZoVx-HEE
+youtube szDN9Vizof0
+youtube WmAgBGttM1U
+youtube u2LMzgzfTn0
+youtube N9jToGJ4ItU
+youtube fG04lEYBQ5Y
+youtube ZO1xdXOXarc
+youtube 2unofxOxQyg
+youtube F3lki7udJU0
+youtube 4MPKqD4xOnk
+youtube -9fC6oDFl5k
+youtube 4lTi7ZGGma8
+youtube IHp5Y4S3IJQ
+youtube 9_P-2DrV5qw
+youtube Bfn3BMV3LOQ
+youtube u1uvv_yKhH8
+youtube NFc438SCPls
+youtube K0VzPBVdiZU
+youtube Lk3UlEyIW84
+youtube w-LYB32COdU
+youtube 4wtzi6IBu94
+youtube H0G1ta9qZfs
+youtube 8twWrJEoNwg
+youtube wFA6h83CLSw
+youtube 9SR_QJvQ74E
+youtube beUPi1_KaCA
+youtube NcTbjGxZnL0
+youtube lCNDcqDYFS4
+youtube 3tyHuVLCldM
+youtube VDUBi41X8qI
+youtube MHmKx-QFIr4
+youtube oPVxdNe1hrk
+youtube nqfVoTMEosw
+youtube ilV_8qWh63o
+youtube xK5g6wSY8D8
+youtube WqJlCgm0FpY
+youtube u88RDsCikd0
+youtube q0Bc1lmn5fA
+youtube 2_P5kXPY8nI
+youtube XeR5lk8pPRU
+youtube ln_jsj2L8uI
+youtube SB03Cbc50i8
+youtube ITtUs0fKmPM
+youtube lQcnNPqy2Ww
+youtube iv7TrKrwteI
+youtube OKsqczjP5-8
+youtube IeCYQrdEshc
+youtube X66MOXDtubE
+youtube lmOUj3xqGcM
+youtube 0FF2Igmw24s
+youtube oLsCDpAldNY
+youtube lGvHIB2VfWc
+youtube 6VQJViW_h30
+youtube ic9KOmxNrDc
+youtube AaECVchAYyQ
+youtube BD6w84Qwv6g
+youtube 0YCPLrxcdXE
+youtube 2yoIPB8sowA
+youtube qtgf-sidZrU
+youtube kNvb260yGas
+youtube Tf32s5i0DRI
+youtube jnLaKMKZz98
+youtube yQFBKx6eq8s
+youtube 1aPb6EyK5j0
+youtube C1D3G2VGQ_8
+youtube 4EQkYVtE-28
+youtube iIl586rP9_s
+youtube iMbY4AKTITU
+youtube sJ1lDeMfwlI
+youtube sIwxf8G3Bds
+youtube VqC-3G5XW70
+youtube cEGG77nmrTI
+youtube MYrSpF20Scg
+youtube 6ZWpq-U_ndc
+youtube 0mPXpFb0ZqY
+youtube wt2qlecXbUc
+youtube agH_5lN6rqA
+youtube JTgpSpmJBmc
+youtube 0t0HtFYjcU8
+youtube KLUKCNH9vBE
+youtube RF_PZ2pf2Fs
+youtube lzZ9r3DK8Bc
+youtube bneBE_tIWBo
+youtube AySMGpXQ1Mo
+youtube nOxTr2OgYGE
+youtube CxSK2x5pPhk
+youtube TlvFo3umL1c
+youtube 84cWrF3m2t0
+youtube O6tXw1BmJaM
+youtube 4kqx05jGAeQ
+youtube lbLeVCAqd7Y
+youtube 3QU2Rr6pTz8
+youtube oOp_p3sl_Dc
+youtube MIOn_yEwvrk
+youtube WMF4kot2qGA
+youtube DVrWMkNQ5A0
+youtube 2yqYAsUpdSc
+youtube RUFU0Qq1eQ8
+youtube iBcT8tTOgE4
+youtube NmdXQ3xS6cw
+youtube 04P3Fr145iY
+youtube J5RFlPtdHB0
+youtube XfbslWb73nc
+youtube RyaC1BmJdi8
+youtube Y2L0N_jDBRU
+youtube c0uekC4DJak
+youtube 3YHGEuefsbI
+youtube xrtNdl80COo
+youtube CWz7zABB3nQ
+youtube 6o1frMV8quk
+youtube 8gl63OTbdL4
+youtube dtuvLxGvDTY
+youtube _s5bvvX_pug
+youtube 8_F966JZw0Y
+youtube J8N2oytbAiY
+youtube x-C4ZE0UZX8
+youtube mzY6Mw8oAEM
+youtube d9bF2En21IE
+youtube 6s9pKLqvjlw
+youtube 7qcHUYL6JWA
+youtube 3kpPJVznpwE
+youtube MnYR0fAoH_A
+youtube ORgOsy_9WB8
+youtube 5CQhtD3TZQU
+youtube IweuqB1_hB8
+youtube Cwj9kN-L-6w
+youtube tee3dHE1w7Q
+youtube UOf6uHwB3Tk
+youtube FIsCYzu_1Gc
+youtube gjzcZ2djHUY
+youtube GPNH0xdQd5I
+youtube cFNC0mNtCiE
+youtube irkNILKWI8Y
+youtube DwBTeO_FIEg
+youtube Sph8C9j2EwI
+youtube ZXPnPwgTYbQ
+youtube 0tTn95TLIaw
+youtube Nx_jutZpvmo
+youtube eXbTZrWUw1k
+youtube xQht_yN7yPE
+youtube 2YKFLgQY12o
+youtube hzLuBxF1Z9A
+youtube OgiahIH6jfk
+youtube YSJ9_lP5sgI
+youtube 6gNgkmuJ45E
+youtube r7Rn4ryE_w8
+youtube xzsIhN3mf-E
+youtube vy0O0okHiXs
+youtube gDfBzCed95A
+youtube X3To4b65uhU
+youtube q-YzHC6m7Po
+youtube tEpuiZm94OQ
+youtube QV8D6P-NR4c
+youtube 7pfX1OKqP80
+youtube 4sdN33l2WTw
+youtube gZhOfQgOJuY
+youtube ZDvtOWWsH7Q
+youtube qSGnXzFNfOA
+youtube vm3T2sdeIOY
+youtube KvZLvJc_ry8
+youtube 1MGTRIIwBdo
+youtube XNSmuTpzr8U
+youtube NAQdo4KMzl4
+youtube Yp8ZPmxb5EY
+youtube CKj6eAZgtCk
+youtube S97DVlwfHNY
+youtube 4J55qoGV-n8
+youtube Qbg7U5SpHnU
+youtube HBakFdvNXoE
+youtube uQsez2zKjEU
+youtube 8MVKocloeiQ
+youtube ipkaSc30uXw
+youtube Hm8cUhc4tWM
+youtube _JdHseLxSTA
+youtube uElVEYzTIag
+youtube XedT5nYyq54
+youtube jI_POmgGzkk
+youtube jJ2r00deAzQ
+youtube NvVIgGcaT8Q
+youtube pprjzeGiJJ0
+youtube gZ_UTJIwIdk
+youtube RRic28k8sVU
+youtube d5p6TceFInU
+youtube iUlKoMF03jk
+youtube -oV6ARCm3ic
+youtube pmc93sA80HI
+youtube Sk_EViIzpU8
+youtube kYaiRDshuk8
+youtube kvfKCc7BpsA
+youtube JApegyYlvyY
+youtube fSzLmXV9pIo
+youtube RcSX0hOWcQ0
+youtube vabmziwg88U
+youtube -RqRdRoEzJo
+youtube _qHjHkcdUt0
+youtube yUwyBlCEvGY
+youtube t8P-zdkoeJA
+youtube ynFW5QvYFg8
+youtube ged4anVdjb0
+youtube PT4euYnkSx4
+youtube 0Yi2qr0_zXY
+youtube o6YGhFmCd4s
+youtube vD0ZkIibN54
+youtube _7qSQS6kg44
+youtube Hg0M9zbUHLc
+youtube DMkPKcggybY
+youtube V_NZoVc_1GQ
+youtube X1R7SrjI_o8
+youtube 1-fDgT5ze84
+youtube LKZyp2cSAy4
+youtube yLwHUlAi8-Y
+youtube u_TXoOJVSYc
+youtube mpdIaUzG1RQ
+youtube d57qSDCxVA4
+youtube vum2bUJwfkI
+youtube tf6iaFf6mbA
+youtube fH1PcHx2kPY
+youtube OiIUDqiCuu8
+youtube eXLPdJR-9oE
+youtube uZdhFN7xQJY
+youtube Bf4ofnIVJ7w
+youtube ElrqPgGSBkg
+youtube 2fSAEDfAGMA
+youtube SsCM36uhdzA
+youtube 6U2h_iccLXE
+youtube u5UBeMWq2tU
+youtube 6FejLgVk-lg
+youtube 1tdzeOcEhzI
+youtube ZEsjZ-49uso
+youtube CWqdowLFRKw
+youtube IMwpwzMc2gw
+youtube oJAWNEUvIoU
+youtube Lhm3FKa76is
+youtube OhqyRJtv3K0
+youtube b88dtuQoXBE
+youtube XHAw9Iw7_i4
+youtube QVGwJs0dm7M
+youtube a_vF9A0E-CU
+youtube plFQxxI93Us
+youtube A4ObRCfycFU
+youtube mKpDqPXjwYk
+youtube rUtl2YAbAiQ
+youtube OkQ-jalcU0k
+youtube WEH5_T5ViEA
+youtube jnfmfXXJUMg
+youtube Kx8pJzPodsY
+youtube xnE16OxNeJ0
+youtube 49cyaEDTd5I
+youtube X-cpj00dVvs
+youtube 8neCjSjIzbs
+youtube Kxz96F7Lcnc
+youtube fYDL9aHmDy4
+youtube SW0GqiTVR30
+youtube l4YzAX67sEM
+youtube oMYVd15rZZE
+youtube 6fgRDuUxnBU
+youtube 4IKid9FzwiQ
+youtube Kp1DpYRxBRY
+youtube 7p_uBs8URdk
+youtube 1EHb_PwBu4c
+youtube NAQtPzb6pAc
+youtube S8gYErP4ZDQ
+youtube MJSmazt9epw
+youtube uzO5baQ_0bc
+youtube r3PZv4b97l4
+youtube p9DfR6Teagg
+youtube 4j6ZYUXj24o
+youtube _ZkUb7iIOqQ
+youtube 2oYi6SQh0xg
+youtube m4racJaB-h4
+youtube DCsStsLns34
+youtube DoA4UbVXh3A
+youtube HSW0GI_BnB8
+youtube Mw5mAozjC6M
+youtube Va04cE58zus
+youtube kk-dwdwnqgI
+youtube GDyN6FgwjGA
+youtube yz7yU3f8Qt8
+youtube sEYo-5BnYvM
+youtube lFkHO9GXDAk
+youtube H1hxHpqmawY
+youtube usUroAXFKF4
+youtube SRgRY3yII4c
+youtube QDQYVFQGkkw
+youtube V9jyksFEhaA
+youtube Kc5Ejht0eGY
+youtube jXdydgAf0BE
+youtube XQnyurnyViY
+youtube C46zC8lebXY
+youtube 5rnawnfK2sQ
+youtube 7BCNh8umDO0
+youtube oTth_Tm-LaQ
+youtube R5f-IpCkU4o
+youtube 4Ukh9aQBzWc
+youtube 9oI79ezMO9Y
+youtube twsetQUQd4A
+youtube 2IzuShoLEHg
+youtube wGS8CsuiihE
+youtube s6H6PCtSSwg
+youtube C0OExq-h2Uw
+youtube ngVp6RcLYXw
+youtube p8-IEhPEqzc
+youtube f8NmAx4L0pE
+youtube TC6GFitReGc
+youtube dRUp_8mVezk
+youtube nU2ESuG0SLk
+youtube Yziapg-_JZQ
+youtube 7374CZQoS2Y
+youtube 2n89HaWYrxo
+youtube LCpjdohpuEE
+youtube qAsHVwl-MU4
+youtube x6EieknRJuc
+youtube H8VwHCvWQYE
+youtube hJWSZDJb-W4
+youtube v6HFc5I0hr8
+youtube HcOuoNgrhFg
+youtube qHFNBBaEsIs
+youtube fI71L1RamDc
+youtube ptbENF77Yb8
+youtube fwc3xyE17EY
+youtube wZgsxnYqHps
+youtube 97EtSDf6wx8
+youtube 6Yf4tPL6_Xw
+youtube LKUXc1uwA2Y
+youtube FEcfXRPaO90
+youtube Mhyi9p7T7OM
+youtube 9RfVp-GhKfs
+youtube ECVtbdgYQhw
+youtube JVQxUFuvLSg
+youtube NubdUxGGYMQ
+youtube VzAjXdBJsEc
+youtube _y2dRHPSNIk
+youtube 9KvCJi7t2DE
+youtube tJttoZEbvYg
+youtube r_fXEWnstMI
+youtube iXjUF4BBFq4
+youtube qmjdscabCsA
+youtube fT_jeadGl5o
+youtube RKD8sVVTEAA
+youtube 7LvesuacMBg
+youtube lIdPMjWIN44
+youtube Vxy4muA0IWc
+youtube IPL75QBAyH0
+youtube t-Dtx7HNefE
+youtube _0Yp2Wtvpeo
+youtube jXgc1FTjMd4
+youtube Ickos1VMnDk
+youtube A9n7esSpCtY
+youtube OejyNpxR-DM
+youtube m9k7WgIPK14
diff --git a/default/Pictures/resources/hhkb-fn-layout.webp b/default/Pictures/resources/hhkb-fn-layout.webp
new file mode 100644
index 0000000..bebb8c4
--- /dev/null
+++ b/default/Pictures/resources/hhkb-fn-layout.webp
Binary files differ
diff --git a/default/Pictures/resources/hhkb-layout.png b/default/Pictures/resources/hhkb-layout.png
new file mode 100644
index 0000000..9339dcd
--- /dev/null
+++ b/default/Pictures/resources/hhkb-layout.png
Binary files differ
diff --git a/default/Pictures/resources/task-2.3.0.png b/default/Pictures/resources/task-2.3.0.png
new file mode 100644
index 0000000..9088a00
--- /dev/null
+++ b/default/Pictures/resources/task-2.3.0.png
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet1-ko.webp b/default/Pictures/resources/vim-cheatsheet1-ko.webp
new file mode 100644
index 0000000..b64e63c
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet1-ko.webp
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet1.jpg b/default/Pictures/resources/vim-cheatsheet1.jpg
new file mode 100644
index 0000000..03ad705
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet1.jpg
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet2-ko.webp b/default/Pictures/resources/vim-cheatsheet2-ko.webp
new file mode 100644
index 0000000..d8bf593
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet2-ko.webp
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet2.jpg b/default/Pictures/resources/vim-cheatsheet2.jpg
new file mode 100644
index 0000000..6c3a59d
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet2.jpg
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet3.jpg b/default/Pictures/resources/vim-cheatsheet3.jpg
new file mode 100644
index 0000000..bbe7d9f
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet3.jpg
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet4.jpg b/default/Pictures/resources/vim-cheatsheet4.jpg
new file mode 100644
index 0000000..a920333
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet4.jpg
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet5.jpg b/default/Pictures/resources/vim-cheatsheet5.jpg
new file mode 100644
index 0000000..4b28350
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet5.jpg
Binary files differ
diff --git a/default/Pictures/resources/vim-cheatsheet6.jpg b/default/Pictures/resources/vim-cheatsheet6.jpg
new file mode 100644
index 0000000..5ad119e
--- /dev/null
+++ b/default/Pictures/resources/vim-cheatsheet6.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/alley.jpg b/default/Pictures/wallpaper/alley.jpg
new file mode 100644
index 0000000..d08f2dc
--- /dev/null
+++ b/default/Pictures/wallpaper/alley.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/ani-girl.png b/default/Pictures/wallpaper/ani-girl.png
new file mode 100644
index 0000000..2ca1ad2
--- /dev/null
+++ b/default/Pictures/wallpaper/ani-girl.png
Binary files differ
diff --git a/default/Pictures/wallpaper/aurora-sky.jpg b/default/Pictures/wallpaper/aurora-sky.jpg
new file mode 100644
index 0000000..1847842
--- /dev/null
+++ b/default/Pictures/wallpaper/aurora-sky.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/aurora-tree.jpg b/default/Pictures/wallpaper/aurora-tree.jpg
new file mode 100644
index 0000000..dfbc66a
--- /dev/null
+++ b/default/Pictures/wallpaper/aurora-tree.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/autumn-paint.jpg b/default/Pictures/wallpaper/autumn-paint.jpg
new file mode 100644
index 0000000..70822ff
--- /dev/null
+++ b/default/Pictures/wallpaper/autumn-paint.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/ball.jpg b/default/Pictures/wallpaper/ball.jpg
new file mode 100644
index 0000000..a2d994f
--- /dev/null
+++ b/default/Pictures/wallpaper/ball.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/bedroom.jpg b/default/Pictures/wallpaper/bedroom.jpg
new file mode 100644
index 0000000..e3f07e2
--- /dev/null
+++ b/default/Pictures/wallpaper/bedroom.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/city.png b/default/Pictures/wallpaper/city.png
new file mode 100644
index 0000000..d45fa6b
--- /dev/null
+++ b/default/Pictures/wallpaper/city.png
Binary files differ
diff --git a/default/Pictures/wallpaper/dark-ghost.jpg b/default/Pictures/wallpaper/dark-ghost.jpg
new file mode 100644
index 0000000..d0ab690
--- /dev/null
+++ b/default/Pictures/wallpaper/dark-ghost.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/dark-sky.png b/default/Pictures/wallpaper/dark-sky.png
new file mode 100644
index 0000000..fb8c951
--- /dev/null
+++ b/default/Pictures/wallpaper/dark-sky.png
Binary files differ
diff --git a/default/Pictures/wallpaper/flower.jpg b/default/Pictures/wallpaper/flower.jpg
new file mode 100644
index 0000000..1221d35
--- /dev/null
+++ b/default/Pictures/wallpaper/flower.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/forest-road.jpg b/default/Pictures/wallpaper/forest-road.jpg
new file mode 100644
index 0000000..30a4382
--- /dev/null
+++ b/default/Pictures/wallpaper/forest-road.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/horse.jpg b/default/Pictures/wallpaper/horse.jpg
new file mode 100644
index 0000000..f3d3b74
--- /dev/null
+++ b/default/Pictures/wallpaper/horse.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/moon-girl.jpg b/default/Pictures/wallpaper/moon-girl.jpg
new file mode 100644
index 0000000..7c030e5
--- /dev/null
+++ b/default/Pictures/wallpaper/moon-girl.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/moon-space.jpg b/default/Pictures/wallpaper/moon-space.jpg
new file mode 100644
index 0000000..04286dd
--- /dev/null
+++ b/default/Pictures/wallpaper/moon-space.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/mountain.png b/default/Pictures/wallpaper/mountain.png
new file mode 100644
index 0000000..3d73b7c
--- /dev/null
+++ b/default/Pictures/wallpaper/mountain.png
Binary files differ
diff --git a/default/Pictures/wallpaper/night-river.jpg b/default/Pictures/wallpaper/night-river.jpg
new file mode 100644
index 0000000..8d471a2
--- /dev/null
+++ b/default/Pictures/wallpaper/night-river.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/sky.jpg b/default/Pictures/wallpaper/sky.jpg
new file mode 100644
index 0000000..fbb2876
--- /dev/null
+++ b/default/Pictures/wallpaper/sky.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-bubble.jpg b/default/Pictures/wallpaper/snow-bubble.jpg
new file mode 100644
index 0000000..c22d8ce
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-bubble.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-dogs.jpg b/default/Pictures/wallpaper/snow-dogs.jpg
new file mode 100644
index 0000000..6af0782
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-dogs.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-lake.jpg b/default/Pictures/wallpaper/snow-lake.jpg
new file mode 100644
index 0000000..9ca5383
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-lake.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-load.jpg b/default/Pictures/wallpaper/snow-load.jpg
new file mode 100644
index 0000000..a0a4a41
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-load.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-tree.jpg b/default/Pictures/wallpaper/snow-tree.jpg
new file mode 100644
index 0000000..da9100d
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-tree.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-tree2.jpg b/default/Pictures/wallpaper/snow-tree2.jpg
new file mode 100644
index 0000000..e4b771a
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-tree2.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/snow-tree3.jpg b/default/Pictures/wallpaper/snow-tree3.jpg
new file mode 100644
index 0000000..2622ca0
--- /dev/null
+++ b/default/Pictures/wallpaper/snow-tree3.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/space.png b/default/Pictures/wallpaper/space.png
new file mode 100644
index 0000000..f806d59
--- /dev/null
+++ b/default/Pictures/wallpaper/space.png
Binary files differ
diff --git a/default/Pictures/wallpaper/sunset-city.jpg b/default/Pictures/wallpaper/sunset-city.jpg
new file mode 100644
index 0000000..8fa6968
--- /dev/null
+++ b/default/Pictures/wallpaper/sunset-city.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/sunset-tree.jpg b/default/Pictures/wallpaper/sunset-tree.jpg
new file mode 100644
index 0000000..f1b3bf5
--- /dev/null
+++ b/default/Pictures/wallpaper/sunset-tree.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/surfing.jpg b/default/Pictures/wallpaper/surfing.jpg
new file mode 100644
index 0000000..a60c24b
--- /dev/null
+++ b/default/Pictures/wallpaper/surfing.jpg
Binary files differ
diff --git a/default/Pictures/wallpaper/woman.jpg b/default/Pictures/wallpaper/woman.jpg
new file mode 100644
index 0000000..a2430f2
--- /dev/null
+++ b/default/Pictures/wallpaper/woman.jpg
Binary files differ
diff --git a/mac/.config/borders/bordersrc b/mac/.config/borders/bordersrc
new file mode 100644
index 0000000..d043d83
--- /dev/null
+++ b/mac/.config/borders/bordersrc
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+options=(
+ style=square
+ width=6.0
+ hidpi=on
+ active_color=0xff269043
+ # active_color="gradient(top_left=0xffebeb99,bottom_right=0xfff57f17)"
+ inactive_color=0xff4b5356
+)
+
+borders "${options[@]}"
diff --git a/mac/.config/karabiner/assets/complex_modifications/1700711318.json b/mac/.config/karabiner/assets/complex_modifications/1700711318.json
new file mode 100644
index 0000000..970adb0
--- /dev/null
+++ b/mac/.config/karabiner/assets/complex_modifications/1700711318.json
@@ -0,0 +1,95 @@
+{
+ "title": "fn twice -> change input source / fn hold -> iterm",
+ "author": "Ramiro Garcia (https://github.com/ranemirusG)",
+ "rules": [
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "type": "basic",
+ "from": {
+ "key_code": 53,
+ "modifiers": { "mandatory": ["fn"] }
+ },
+ "to": [{ "shell_command": "open '/Applications/iTerm.app'" }]
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "type": "variable_if",
+ "name": "fn pressed",
+ "value": 1
+ }
+ ],
+ "type": "basic",
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "repeat": false,
+ "key_code": "spacebar",
+ "modifiers": ["left_control", "left_alt"],
+ "lazy": true
+ }
+ ]
+ },
+ {
+ "type": "basic",
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to_delayed_action": {
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/mac/.config/karabiner/assets/complex_modifications/1700983079.json b/mac/.config/karabiner/assets/complex_modifications/1700983079.json
new file mode 100644
index 0000000..3bdfd01
--- /dev/null
+++ b/mac/.config/karabiner/assets/complex_modifications/1700983079.json
@@ -0,0 +1,62 @@
+{
+ "title": "Use ctrl + hl to Switch Tabs in Chrome, Brave & Firefox",
+ "rules": [
+ {
+ "description": "Use ctrl + hl to Switch Tabs in Chrome, Brave & Firefox",
+ "manipulators": [
+ {
+ "type": "basic",
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": ["control"],
+ "optional": ["any"]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow",
+ "modifiers": ["command", "option"]
+ }
+ ],
+ "conditions": [
+ {
+ "type": "frontmost_application_if",
+ "bundle_identifiers": [
+ "^com.google.Chrome",
+ "^com.brave.Browser",
+ "^org.mozilla.firefox"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "basic",
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": ["control"],
+ "optional": ["any"]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow",
+ "modifiers": ["command", "option"]
+ }
+ ],
+ "conditions": [
+ {
+ "type": "frontmost_application_if",
+ "bundle_identifiers": [
+ "^com.google.Chrome",
+ "^com.brave.Browser",
+ "^org.mozilla.firefox"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/mac/.config/karabiner/assets/complex_modifications/1704627767.json b/mac/.config/karabiner/assets/complex_modifications/1704627767.json
new file mode 100644
index 0000000..71909d1
--- /dev/null
+++ b/mac/.config/karabiner/assets/complex_modifications/1704627767.json
@@ -0,0 +1,20 @@
+{
+ "title": "Prevent unintended command-h hide window (rev 2)",
+ "rules": [
+ {
+ "description": "Disable Cmd+H Hide (rev 2)",
+ "manipulators": [
+ {
+ "type": "basic",
+ "description": "",
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": ["command"]
+ }
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/mac/.config/karabiner/assets/complex_modifications/1704850809.json b/mac/.config/karabiner/assets/complex_modifications/1704850809.json
new file mode 100644
index 0000000..2f3271f
--- /dev/null
+++ b/mac/.config/karabiner/assets/complex_modifications/1704850809.json
@@ -0,0 +1,25 @@
+{
+ "title": "esc = ctrl + c",
+ "rules": [
+ {
+ "description": "Control + c => Escape",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "c",
+ "modifiers": {
+ "mandatory": ["control"],
+ "optional": ["caps_lock", "option"]
+ }
+ },
+ "to": [
+ {
+ "key_code": "escape"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+}
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20231122.json b/mac/.config/karabiner/automatic_backups/karabiner_20231122.json
new file mode 100644
index 0000000..ea801fd
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20231122.json
@@ -0,0 +1,200 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": []
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20231126.json b/mac/.config/karabiner/automatic_backups/karabiner_20231126.json
new file mode 100644
index 0000000..62cc54f
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20231126.json
@@ -0,0 +1,393 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [
+ {
+ "from": {
+ "key_code": "caps_lock"
+ },
+ "to": [
+ {
+ "key_code": "left_control"
+ }
+ ]
+ }
+ ],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20231128.json b/mac/.config/karabiner/automatic_backups/karabiner_20231128.json
new file mode 100644
index 0000000..7161b59
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20231128.json
@@ -0,0 +1,464 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Use ctrl + hl to Switch Tabs in Chrome, Brave & Firefox",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "bundle_identifiers": [
+ "^com.google.Chrome",
+ "^com.brave.Browser",
+ "^org.mozilla.firefox"
+ ],
+ "type": "frontmost_application_if"
+ }
+ ],
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "control"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow",
+ "modifiers": [
+ "command",
+ "option"
+ ]
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "conditions": [
+ {
+ "bundle_identifiers": [
+ "^com.google.Chrome",
+ "^com.brave.Browser",
+ "^org.mozilla.firefox"
+ ],
+ "type": "frontmost_application_if"
+ }
+ ],
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "control"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow",
+ "modifiers": [
+ "command",
+ "option"
+ ]
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [
+ {
+ "from": {
+ "key_code": "caps_lock"
+ },
+ "to": [
+ {
+ "key_code": "left_control"
+ }
+ ]
+ }
+ ],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240107.json b/mac/.config/karabiner/automatic_backups/karabiner_20240107.json
new file mode 100644
index 0000000..62cc54f
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240107.json
@@ -0,0 +1,393 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "identifiers": {
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [
+ {
+ "from": {
+ "key_code": "caps_lock"
+ },
+ "to": [
+ {
+ "key_code": "left_control"
+ }
+ ]
+ }
+ ],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240108.json b/mac/.config/karabiner/automatic_backups/karabiner_20240108.json
new file mode 100644
index 0000000..0088e24
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240108.json
@@ -0,0 +1,417 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [
+ {
+ "from": {
+ "key_code": "caps_lock"
+ },
+ "to": [
+ {
+ "key_code": "left_control"
+ }
+ ]
+ }
+ ],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240109.json b/mac/.config/karabiner/automatic_backups/karabiner_20240109.json
new file mode 100644
index 0000000..0088e24
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240109.json
@@ -0,0 +1,417 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [
+ {
+ "from": {
+ "key_code": "caps_lock"
+ },
+ "to": [
+ {
+ "key_code": "left_control"
+ }
+ ]
+ }
+ ],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240112.json b/mac/.config/karabiner/automatic_backups/karabiner_20240112.json
new file mode 100644
index 0000000..2b1171d
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240112.json
@@ -0,0 +1,461 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": true,
+ "product_id": 45924,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 45085,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [
+ {
+ "from": {
+ "key_code": "caps_lock"
+ },
+ "to": [
+ {
+ "key_code": "left_control"
+ }
+ ]
+ }
+ ],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240113.json b/mac/.config/karabiner/automatic_backups/karabiner_20240113.json
new file mode 100644
index 0000000..13346ea
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240113.json
@@ -0,0 +1,472 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "Change right_command+hjkl to arrow keys",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": "h",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "left_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "j",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "down_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "k",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "up_arrow"
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "l",
+ "modifiers": {
+ "mandatory": [
+ "right_command"
+ ],
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "right_arrow"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": true,
+ "product_id": 45924,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 45085,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 256,
+ "vendor_id": 2131
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240114.json b/mac/.config/karabiner/automatic_backups/karabiner_20240114.json
new file mode 100644
index 0000000..9193d0d
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240114.json
@@ -0,0 +1,391 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": true,
+ "product_id": 45924,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 45085,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 256,
+ "vendor_id": 2131
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/automatic_backups/karabiner_20240115.json b/mac/.config/karabiner/automatic_backups/karabiner_20240115.json
new file mode 100644
index 0000000..9193d0d
--- /dev/null
+++ b/mac/.config/karabiner/automatic_backups/karabiner_20240115.json
@@ -0,0 +1,391 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": [
+ {
+ "description": "fn + ` -> iTerm",
+ "manipulators": [
+ {
+ "from": {
+ "key_code": 53,
+ "modifiers": {
+ "mandatory": [
+ "fn"
+ ]
+ }
+ },
+ "to": [
+ {
+ "shell_command": "open '/Applications/iTerm.app'"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ },
+ {
+ "description": "Press twice fn to change input source & otherwise held_down and open iterm",
+ "manipulators": [
+ {
+ "conditions": [
+ {
+ "name": "fn pressed",
+ "type": "variable_if",
+ "value": 1
+ }
+ ],
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "to": [
+ {
+ "key_code": "spacebar",
+ "lazy": true,
+ "modifiers": [
+ "left_control",
+ "left_alt"
+ ],
+ "repeat": false
+ }
+ ],
+ "type": "basic"
+ },
+ {
+ "from": {
+ "key_code": "fn",
+ "modifiers": {
+ "optional": [
+ "any"
+ ]
+ }
+ },
+ "parameters": {
+ "basic.to_if_held_down_threshold_milliseconds": 0
+ },
+ "to": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 1
+ }
+ }
+ ],
+ "to_delayed_action": {
+ "to_if_canceled": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ],
+ "to_if_invoked": [
+ {
+ "set_variable": {
+ "name": "fn pressed",
+ "value": 0
+ }
+ }
+ ]
+ },
+ "to_if_held_down": [
+ {
+ "key_code": "fn"
+ }
+ ],
+ "type": "basic"
+ }
+ ]
+ }
+ ]
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": true,
+ "product_id": 45924,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 45085,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 256,
+ "vendor_id": 2131
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/karabiner/karabiner.json b/mac/.config/karabiner/karabiner.json
new file mode 100644
index 0000000..db9c040
--- /dev/null
+++ b/mac/.config/karabiner/karabiner.json
@@ -0,0 +1,290 @@
+{
+ "global": {
+ "ask_for_confirmation_before_quitting": true,
+ "check_for_updates_on_startup": true,
+ "show_in_menu_bar": true,
+ "show_profile_name_in_menu_bar": false,
+ "unsafe_ui": false
+ },
+ "profiles": [
+ {
+ "complex_modifications": {
+ "parameters": {
+ "basic.simultaneous_threshold_milliseconds": 50,
+ "basic.to_delayed_action_delay_milliseconds": 500,
+ "basic.to_if_alone_timeout_milliseconds": 1000,
+ "basic.to_if_held_down_threshold_milliseconds": 500,
+ "mouse_motion_to_scroll.speed": 100
+ },
+ "rules": []
+ },
+ "devices": [
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 0,
+ "vendor_id": 0
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 34304,
+ "vendor_id": 1452
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": true,
+ "product_id": 45924,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": false,
+ "is_pointing_device": true,
+ "product_id": 45085,
+ "vendor_id": 1133
+ },
+ "ignore": true,
+ "manipulate_caps_lock_led": false,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ },
+ {
+ "disable_built_in_keyboard_if_exists": false,
+ "fn_function_keys": [],
+ "game_pad_swap_sticks": false,
+ "identifiers": {
+ "is_game_pad": false,
+ "is_keyboard": true,
+ "is_pointing_device": false,
+ "product_id": 256,
+ "vendor_id": 2131
+ },
+ "ignore": false,
+ "manipulate_caps_lock_led": true,
+ "mouse_flip_horizontal_wheel": false,
+ "mouse_flip_vertical_wheel": false,
+ "mouse_flip_x": false,
+ "mouse_flip_y": false,
+ "mouse_swap_wheels": false,
+ "mouse_swap_xy": false,
+ "simple_modifications": [],
+ "treat_as_built_in_keyboard": false
+ }
+ ],
+ "fn_function_keys": [
+ {
+ "from": {
+ "key_code": "f1"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f2"
+ },
+ "to": [
+ {
+ "consumer_key_code": "display_brightness_increment"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f3"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "mission_control"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f4"
+ },
+ "to": [
+ {
+ "apple_vendor_keyboard_key_code": "spotlight"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f5"
+ },
+ "to": [
+ {
+ "consumer_key_code": "dictation"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f6"
+ },
+ "to": [
+ {
+ "key_code": "f6"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f7"
+ },
+ "to": [
+ {
+ "consumer_key_code": "rewind"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f8"
+ },
+ "to": [
+ {
+ "consumer_key_code": "play_or_pause"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f9"
+ },
+ "to": [
+ {
+ "consumer_key_code": "fast_forward"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f10"
+ },
+ "to": [
+ {
+ "consumer_key_code": "mute"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f11"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_decrement"
+ }
+ ]
+ },
+ {
+ "from": {
+ "key_code": "f12"
+ },
+ "to": [
+ {
+ "consumer_key_code": "volume_increment"
+ }
+ ]
+ }
+ ],
+ "name": "Default profile",
+ "parameters": {
+ "delay_milliseconds_before_open_device": 1000
+ },
+ "selected": true,
+ "simple_modifications": [],
+ "virtual_hid_keyboard": {
+ "country_code": 0,
+ "indicate_sticky_modifier_keys_state": true,
+ "mouse_key_xy_scale": 100
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/mac/.config/kitty/icons/kitty-dark.icns b/mac/.config/kitty/icons/kitty-dark.icns
new file mode 100644
index 0000000..57e2982
--- /dev/null
+++ b/mac/.config/kitty/icons/kitty-dark.icns
Binary files differ
diff --git a/mac/.config/kitty/icons/kitty-light.icns b/mac/.config/kitty/icons/kitty-light.icns
new file mode 100644
index 0000000..0ac7304
--- /dev/null
+++ b/mac/.config/kitty/icons/kitty-light.icns
Binary files differ
diff --git a/mac/.config/kitty/icons/neue_azure.icns b/mac/.config/kitty/icons/neue_azure.icns
new file mode 100644
index 0000000..6573b4c
--- /dev/null
+++ b/mac/.config/kitty/icons/neue_azure.icns
Binary files differ
diff --git a/mac/.config/kitty/icons/neue_ember.icns b/mac/.config/kitty/icons/neue_ember.icns
new file mode 100644
index 0000000..e8adb6d
--- /dev/null
+++ b/mac/.config/kitty/icons/neue_ember.icns
Binary files differ
diff --git a/mac/.config/kitty/icons/neue_outrun.icns b/mac/.config/kitty/icons/neue_outrun.icns
new file mode 100644
index 0000000..65f1ce6
--- /dev/null
+++ b/mac/.config/kitty/icons/neue_outrun.icns
Binary files differ
diff --git a/mac/.config/kitty/icons/neue_toxic.icns b/mac/.config/kitty/icons/neue_toxic.icns
new file mode 100644
index 0000000..67ff000
--- /dev/null
+++ b/mac/.config/kitty/icons/neue_toxic.icns
Binary files differ
diff --git a/mac/.config/kitty/icons/outrun.icns b/mac/.config/kitty/icons/outrun.icns
new file mode 100644
index 0000000..3ab11a4
--- /dev/null
+++ b/mac/.config/kitty/icons/outrun.icns
Binary files differ
diff --git a/mac/.config/kitty/kitty.conf b/mac/.config/kitty/kitty.conf
new file mode 100644
index 0000000..5662155
--- /dev/null
+++ b/mac/.config/kitty/kitty.conf
@@ -0,0 +1,145 @@
+# Font
+font_family FiraCode Nerd Font
+italic_font auto
+bold_italic_font auto
+font_size 16.0
+
+underline_position -2
+modify_font underline_thickness 150%
+modify_font strikethrough_position 2px
+text_composition_strategy platform
+
+# Sound
+enable_audio_bell no
+
+# Cursor
+cursor_text_color background
+cursor_shape block
+cursor_blink_interval 0
+scrollback_lines 10000
+copy_on_select clipboard
+strip_trailing_spaces always
+select_by_word_characters @-./_~?&=%+#
+
+# Window
+confirm_os_window_close 0
+# remember_window_size yes
+# initial_window_width 640
+# initial_window_height 400
+# enabled_layouts *
+# draw_minimal_borders yes
+resize_in_steps no
+# window_resize_step_cells 0
+# window_resize_step_lines 0
+placement_strategy center
+# window_border_width 100px
+window_margin_width 5
+# single_window_margin_width -1
+hide_window_decorations yes
+# hide_window_decorations titlebar-only
+
+# Tab bar
+tab_bar_edge top
+active_tab_foreground #000
+active_tab_background #eee
+active_tab_font_style bold-italic
+inactive_tab_foreground #444
+inactive_tab_background #999
+inactive_tab_font_style normal
+# Background
+background_opacity 0.9
+# background_image /Users/si/.config/background/bg_girl.png
+# background_image /Users/si/.config/background/yejin.png
+background_image_layout cscaled
+background_image_linear no
+dynamic_background_opacity yes
+# background_tint 0.95
+# background_tint_gaps 1.0
+# dim_opacity 0.1
+
+# Remote
+allow_remote_control yes
+
+# Macos
+macos_titlebar_color system
+macos_option_as_alt left
+macos_window_resizable yes
+
+# Keybinding
+# kitty_mod ctrl+shift
+map cmd+n no_op
+map cmd+enter no_op
+map cmd+t no_op
+map cmd+w no_op
+map cmd+r no_op
+map cmd+shift+i no_op
+map cmd+shift+o no_op
+map cmd+shift+p no_op
+map cmd+c copy_to_clipboard
+map cmd+v paste_from_clipboard
+map kitty_mod+w close_window
+# map cmd+] next_window
+# map cmd+[ previous_window
+# map cmd+1 first_window
+# map cmd+2 second_window
+# map cmd+3 third_window
+# map cmd+4 fourth_window
+# map cmd+5 fifth_window
+
+# Theme
+# include ./theme.conf
+term xterm-256color
+# selection_foreground #000000
+# selection_background #fffacd
+# foreground #f8f8f2
+# foreground #e0d0b0
+# background #000000
+# background #151515
+# selection_foreground #151515
+# selection_background #ffa9c9
+# cursor #ffa9c9
+# cursor #f8f8f2
+# cursor_text_color #151515
+# url_color #95b9d0
+url_color #d65c9d
+active_border_color #ffa9c9
+# active_border_color #00ff00
+# inactive_border_color #928374
+inactive_border_color #cccccc
+bell_border_color #f98080
+# wayland_titlebar_color #080808
+# macos_titlebar_color #080808
+active_tab_foreground #080808
+active_tab_background #a990c9
+inactive_tab_foreground #bdae93
+inactive_tab_background #252525
+tab_bar_background #080808
+# mark1_foreground #080808
+# mark1_background #89a0e0
+# mark2_foreground #080808
+# mark2_background #95b9d0
+# mark3_foreground #080808
+# mark3_background #a0d0c0
+# color0 #000000
+# color0 #3a3634
+# color8 #504844
+# color8 #44475a
+# color1 #ff6960
+# color9 #f98080
+# color1 #ff5555
+# color9 #ff5555
+# color2 #b0d080
+# color10 #c0e990
+# color3 #f9bd69
+# color11 #ffd079
+# color4 #89a0e0
+# color12 #95b9d0
+# color5 #df95cf
+# color13 #ffa9c9
+# color6 #a0d0c0
+# color14 #a0dfa9
+# color7 #d5c4a1
+# color15 #bdae93
+# color7 #bbbbbb
+# color15 #ffffff
+
diff --git a/mac/.config/neofetch/config.conf b/mac/.config/neofetch/config.conf
new file mode 100644
index 0000000..69a95cc
--- /dev/null
+++ b/mac/.config/neofetch/config.conf
@@ -0,0 +1,863 @@
+# See this wiki page for more info:
+# https://github.com/dylanaraps/neofetch/wiki/Customizing-Info
+print_info() {
+ # info cols
+ prin " ┌─────────\n Hardware Information \n─────────┐"
+ info " ​ ​  " distro
+ info " ​ ​ 󰻠 " cpu
+ info " ​ ​  " gpu
+ info " ​ ​  " disk
+ info " ​ ​ 󰍛 " memory
+ info " ​ ​ 󰍹 " resolution
+ info " ​ ​ 󰂀 " battery
+ prin " ├─────────\n Software Information \n─────────┤"
+ info " ​ ​  " users
+ info " ​ ​  " distro
+ info " ​ ​  " kernel
+ info " ​ ​  " de
+ info " ​ ​  " wm
+ info " ​ ​  " shell
+ info " ​ ​ 󰂮 " term
+ info " ​ ​  " term_font
+ info " ​ ​ │  " font
+ # info " ​ ​  " theme
+ # info " ​ ​  " icons
+ info " ​ ​  " packages
+ info " ​ ​  " uptime
+ info " ​ ​  " gpu_driver # Linux/macOS only
+ info " ​ ​  " cpu_usage
+ # info " ​ ​ ﱘ " song
+ # [[ "$player" ]] && prin "Music Player" "$player"
+ info " ​ ​  " local_ip
+ info " ​ ​  " public_ip
+ info " ​ ​  " locale # This only works on glibc systems.
+ prin " └────────────────────────────────────────┘"
+ info colsZ
+}
+
+# Title
+
+
+# Hide/Show Fully qualified domain name.
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --title_fqdn
+title_fqdn="off"
+
+
+# Kernel
+
+
+# Shorten the output of the kernel function.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --kernel_shorthand
+# Supports: Everything except *BSDs (except PacBSD and PC-BSD)
+#
+# Example:
+# on: '4.8.9-1-ARCH'
+# off: 'Linux 4.8.9-1-ARCH'
+kernel_shorthand="off"
+
+
+# Distro
+
+
+# Shorten the output of the distro function
+#
+# Default: 'off'
+# Values: 'on', 'tiny', 'off'
+# Flag: --distro_shorthand
+# Supports: Everything except Windows and Haiku
+distro_shorthand="off"
+
+# Show/Hide OS Architecture.
+# Show 'x86_64', 'x86' and etc in 'Distro:' output.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --os_arch
+#
+# Example:
+# on: 'Arch Linux x86_64'
+# off: 'Arch Linux'
+os_arch="on"
+
+
+# Uptime
+
+
+# Shorten the output of the uptime function
+#
+# Default: 'on'
+# Values: 'on', 'tiny', 'off'
+# Flag: --uptime_shorthand
+#
+# Example:
+# on: '2 days, 10 hours, 3 mins'
+# tiny: '2d 10h 3m'
+# off: '2 days, 10 hours, 3 minutes'
+uptime_shorthand="on"
+
+
+# Memory
+
+
+# Show memory pecentage in output.
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --memory_percent
+#
+# Example:
+# on: '1801MiB / 7881MiB (22%)'
+# off: '1801MiB / 7881MiB'
+memory_percent="on"
+
+# Change memory output unit.
+#
+# Default: 'mib'
+# Values: 'kib', 'mib', 'gib'
+# Flag: --memory_unit
+#
+# Example:
+# kib '1020928KiB / 7117824KiB'
+# mib '1042MiB / 6951MiB'
+# gib: ' 0.98GiB / 6.79GiB'
+memory_unit="gib"
+
+
+# Packages
+
+
+# Show/Hide Package Manager names.
+#
+# Default: 'tiny'
+# Values: 'on', 'tiny' 'off'
+# Flag: --package_managers
+#
+# Example:
+# on: '998 (pacman), 8 (flatpak), 4 (snap)'
+# tiny: '908 (pacman, flatpak, snap)'
+# off: '908'
+package_managers="on"
+
+
+# Shell
+
+
+# Show the path to $SHELL
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --shell_path
+#
+# Example:
+# on: '/bin/bash'
+# off: 'bash'
+shell_path="off"
+
+# Show $SHELL version
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --shell_version
+#
+# Example:
+# on: 'bash 4.4.5'
+# off: 'bash'
+shell_version="on"
+
+
+# CPU
+
+
+# CPU speed type
+#
+# Default: 'bios_limit'
+# Values: 'scaling_cur_freq', 'scaling_min_freq', 'scaling_max_freq', 'bios_limit'.
+# Flag: --speed_type
+# Supports: Linux with 'cpufreq'
+# NOTE: Any file in '/sys/devices/system/cpu/cpu0/cpufreq' can be used as a value.
+speed_type="bios_limit"
+
+# CPU speed shorthand
+#
+# Default: 'off'
+# Values: 'on', 'off'.
+# Flag: --speed_shorthand
+# NOTE: This flag is not supported in systems with CPU speed less than 1 GHz
+#
+# Example:
+# on: 'i7-6500U (4) @ 3.1GHz'
+# off: 'i7-6500U (4) @ 3.100GHz'
+speed_shorthand="off"
+
+# Enable/Disable CPU brand in output.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --cpu_brand
+#
+# Example:
+# on: 'Intel i7-6500U'
+# off: 'i7-6500U (4)'
+cpu_brand="on"
+
+# CPU Speed
+# Hide/Show CPU speed.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --cpu_speed
+#
+# Example:
+# on: 'Intel i7-6500U (4) @ 3.1GHz'
+# off: 'Intel i7-6500U (4)'
+cpu_speed="on"
+
+# CPU Cores
+# Display CPU cores in output
+#
+# Default: 'logical'
+# Values: 'logical', 'physical', 'off'
+# Flag: --cpu_cores
+# Support: 'physical' doesn't work on BSD.
+#
+# Example:
+# logical: 'Intel i7-6500U (4) @ 3.1GHz' (All virtual cores)
+# physical: 'Intel i7-6500U (2) @ 3.1GHz' (All physical cores)
+# off: 'Intel i7-6500U @ 3.1GHz'
+cpu_cores="logical"
+
+# CPU Temperature
+# Hide/Show CPU temperature.
+# Note the temperature is added to the regular CPU function.
+#
+# Default: 'off'
+# Values: 'C', 'F', 'off'
+# Flag: --cpu_temp
+# Supports: Linux, BSD
+# NOTE: For FreeBSD and NetBSD-based systems, you'll need to enable
+# coretemp kernel module. This only supports newer Intel processors.
+#
+# Example:
+# C: 'Intel i7-6500U (4) @ 3.1GHz [27.2°C]'
+# F: 'Intel i7-6500U (4) @ 3.1GHz [82.0°F]'
+# off: 'Intel i7-6500U (4) @ 3.1GHz'
+cpu_temp="off"
+
+
+# GPU
+
+
+# Enable/Disable GPU Brand
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --gpu_brand
+#
+# Example:
+# on: 'AMD HD 7950'
+# off: 'HD 7950'
+gpu_brand="on"
+
+# Which GPU to display
+#
+# Default: 'all'
+# Values: 'all', 'dedicated', 'integrated'
+# Flag: --gpu_type
+# Supports: Linux
+#
+# Example:
+# all:
+# GPU1: AMD HD 7950
+# GPU2: Intel Integrated Graphics
+#
+# dedicated:
+# GPU1: AMD HD 7950
+#
+# integrated:
+# GPU1: Intel Integrated Graphics
+gpu_type="all"
+
+
+# Resolution
+
+
+# Display refresh rate next to each monitor
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --refresh_rate
+# Supports: Doesn't work on Windows.
+#
+# Example:
+# on: '1920x1080 @ 60Hz'
+# off: '1920x1080'
+refresh_rate="off"
+
+
+# Gtk Theme / Icons / Font
+
+
+# Shorten output of GTK Theme / Icons / Font
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --gtk_shorthand
+#
+# Example:
+# on: 'Numix, Adwaita'
+# off: 'Numix [GTK2], Adwaita [GTK3]'
+gtk_shorthand="off"
+
+
+# Enable/Disable gtk2 Theme / Icons / Font
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --gtk2
+#
+# Example:
+# on: 'Numix [GTK2], Adwaita [GTK3]'
+# off: 'Adwaita [GTK3]'
+gtk2="on"
+
+# Enable/Disable gtk3 Theme / Icons / Font
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --gtk3
+#
+# Example:
+# on: 'Numix [GTK2], Adwaita [GTK3]'
+# off: 'Numix [GTK2]'
+gtk3="on"
+
+
+# IP Address
+
+
+# Website to ping for the public IP
+#
+# Default: 'http://ident.me'
+# Values: 'url'
+# Flag: --ip_host
+public_ip_host="http://ident.me"
+
+# Public IP timeout.
+#
+# Default: '2'
+# Values: 'int'
+# Flag: --ip_timeout
+public_ip_timeout=2
+
+
+# Desktop Environment
+
+
+# Show Desktop Environment version
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --de_version
+de_version="on"
+
+
+# Disk
+
+
+# Which disks to display.
+# The values can be any /dev/sdXX, mount point or directory.
+# NOTE: By default we only show the disk info for '/'.
+#
+# Default: '/'
+# Values: '/', '/dev/sdXX', '/path/to/drive'.
+# Flag: --disk_show
+#
+# Example:
+# disk_show=('/' '/dev/sdb1'):
+# 'Disk (/): 74G / 118G (66%)'
+# 'Disk (/mnt/Videos): 823G / 893G (93%)'
+#
+# disk_show=('/'):
+# 'Disk (/): 74G / 118G (66%)'
+#
+disk_show=('/')
+
+# Disk subtitle.
+# What to append to the Disk subtitle.
+#
+# Default: 'mount'
+# Values: 'mount', 'name', 'dir', 'none'
+# Flag: --disk_subtitle
+#
+# Example:
+# name: 'Disk (/dev/sda1): 74G / 118G (66%)'
+# 'Disk (/dev/sdb2): 74G / 118G (66%)'
+#
+# mount: 'Disk (/): 74G / 118G (66%)'
+# 'Disk (/mnt/Local Disk): 74G / 118G (66%)'
+# 'Disk (/mnt/Videos): 74G / 118G (66%)'
+#
+# dir: 'Disk (/): 74G / 118G (66%)'
+# 'Disk (Local Disk): 74G / 118G (66%)'
+# 'Disk (Videos): 74G / 118G (66%)'
+#
+# none: 'Disk: 74G / 118G (66%)'
+# 'Disk: 74G / 118G (66%)'
+# 'Disk: 74G / 118G (66%)'
+disk_subtitle="mount"
+
+# Disk percent.
+# Show/Hide disk percent.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --disk_percent
+#
+# Example:
+# on: 'Disk (/): 74G / 118G (66%)'
+# off: 'Disk (/): 74G / 118G'
+disk_percent="on"
+
+
+# Song
+
+
+# Manually specify a music player.
+#
+# Default: 'auto'
+# Values: 'auto', 'player-name'
+# Flag: --music_player
+#
+# Available values for 'player-name':
+#
+# amarok
+# audacious
+# banshee
+# bluemindo
+# clementine
+# cmus
+# deadbeef
+# deepin-music
+# dragon
+# elisa
+# exaile
+# gnome-music
+# gmusicbrowser
+# gogglesmm
+# guayadeque
+# io.elementary.music
+# iTunes
+# juk
+# lollypop
+# mocp
+# mopidy
+# mpd
+# muine
+# netease-cloud-music
+# olivia
+# playerctl
+# pogo
+# pragha
+# qmmp
+# quodlibet
+# rhythmbox
+# sayonara
+# smplayer
+# spotify
+# strawberry
+# tauonmb
+# tomahawk
+# vlc
+# xmms2d
+# xnoise
+# yarock
+music_player="auto"
+
+# Format to display song information.
+#
+# Default: '%artist% - %album% - %title%'
+# Values: '%artist%', '%album%', '%title%'
+# Flag: --song_format
+#
+# Example:
+# default: 'Song: Jet - Get Born - Sgt Major'
+song_format="%artist% - %album% - %title%"
+
+# Print the Artist, Album and Title on separate lines
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --song_shorthand
+#
+# Example:
+# on: 'Artist: The Fratellis'
+# 'Album: Costello Music'
+# 'Song: Chelsea Dagger'
+#
+# off: 'Song: The Fratellis - Costello Music - Chelsea Dagger'
+song_shorthand="off"
+
+# 'mpc' arguments (specify a host, password etc).
+#
+# Default: ''
+# Example: mpc_args=(-h HOST -P PASSWORD)
+mpc_args=()
+
+
+# Text Colors
+
+
+# Text Colors
+#
+# Default: 'distro'
+# Values: 'distro', 'num' 'num' 'num' 'num' 'num' 'num'
+# Flag: --colors
+#
+# Each number represents a different part of the text in
+# this order: 'title', '@', 'underline', 'subtitle', 'colon', 'info'
+#
+# Example:
+# colors=(distro) - Text is colored based on Distro colors.
+# colors=(4 6 1 8 8 6) - Text is colored in the order above.
+colors=(distro)
+
+
+# Text Options
+
+
+# Toggle bold text
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --bold
+bold="on"
+
+# Enable/Disable Underline
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --underline
+underline_enabled="on"
+
+# Underline character
+#
+# Default: '-'
+# Values: 'string'
+# Flag: --underline_char
+underline_char="-"
+
+
+# Info Separator
+# Replace the default separator with the specified string.
+#
+# Default: ':'
+# Flag: --separator
+#
+# Example:
+# separator="->": 'Shell-> bash'
+# separator=" =": 'WM = dwm'
+separator=":"
+
+
+# Color Blocks
+
+
+# Color block range
+# The range of colors to print.
+#
+# Default: '0', '15'
+# Values: 'num'
+# Flag: --block_range
+#
+# Example:
+#
+# Display colors 0-7 in the blocks. (8 colors)
+# neofetch --block_range 0 7
+#
+# Display colors 0-15 in the blocks. (16 colors)
+# neofetch --block_range 0 15
+block_range=(0 15)
+
+# Toggle color blocks
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --color_blocks
+color_blocks="on"
+
+# Color block width in spaces
+#
+# Default: '3'
+# Values: 'num'
+# Flag: --block_width
+block_width=3
+
+# Color block height in lines
+#
+# Default: '1'
+# Values: 'num'
+# Flag: --block_height
+block_height=1
+
+# Color Alignment
+#
+# Default: 'auto'
+# Values: 'auto', 'num'
+# Flag: --col_offset
+#
+# Number specifies how far from the left side of the terminal (in spaces) to
+# begin printing the columns, in case you want to e.g. center them under your
+# text.
+# Example:
+# col_offset="auto" - Default behavior of neofetch
+# col_offset=7 - Leave 7 spaces then print the colors
+col_offset="auto"
+
+# Progress Bars
+
+
+# Bar characters
+#
+# Default: '-', '='
+# Values: 'string', 'string'
+# Flag: --bar_char
+#
+# Example:
+# neofetch --bar_char 'elapsed' 'total'
+# neofetch --bar_char '-' '='
+bar_char_elapsed="-"
+bar_char_total="="
+
+# Toggle Bar border
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --bar_border
+bar_border="on"
+
+# Progress bar length in spaces
+# Number of chars long to make the progress bars.
+#
+# Default: '15'
+# Values: 'num'
+# Flag: --bar_length
+bar_length=15
+
+# Progress bar colors
+# When set to distro, uses your distro's logo colors.
+#
+# Default: 'distro', 'distro'
+# Values: 'distro', 'num'
+# Flag: --bar_colors
+#
+# Example:
+# neofetch --bar_colors 3 4
+# neofetch --bar_colors distro 5
+bar_color_elapsed="distro"
+bar_color_total="distro"
+
+
+# Info display
+# Display a bar with the info.
+#
+# Default: 'off'
+# Values: 'bar', 'infobar', 'barinfo', 'off'
+# Flags: --cpu_display
+# --memory_display
+# --battery_display
+# --disk_display
+#
+# Example:
+# bar: '[---=======]'
+# infobar: 'info [---=======]'
+# barinfo: '[---=======] info'
+# off: 'info'
+cpu_display="off"
+memory_display="off"
+battery_display="off"
+disk_display="off"
+
+
+# Backend Settings
+
+
+# Image backend.
+#
+# Default: 'ascii'
+# Values: 'ascii', 'caca', 'chafa', 'jp2a', 'iterm2', 'off',
+# 'pot', 'termpix', 'pixterm', 'tycat', 'w3m', 'kitty'
+# Flag: --backend
+image_backend="ascii"
+
+# Image Source
+#
+# Which image or ascii file to display.
+#
+# Default: 'auto'
+# Values: 'auto', 'ascii', 'wallpaper', '/path/to/img', '/path/to/ascii', '/path/to/dir/'
+# 'command output (neofetch --ascii "$(fortune | cowsay -W 30)")'
+# Flag: --source
+#
+# NOTE: 'auto' will pick the best image source for whatever image backend is used.
+# In ascii mode, distro ascii art will be used and in an image mode, your
+# wallpaper will be used.
+image_source="auto"
+
+
+# Ascii Options
+
+
+# Ascii distro
+# Which distro's ascii art to display.
+#
+# Default: 'auto'
+# Values: 'auto', 'distro_name'
+# Flag: --ascii_distro
+# NOTE: AIX, Alpine, Anarchy, Android, Antergos, antiX, "AOSC OS",
+# "AOSC OS/Retro", Apricity, ArcoLinux, ArchBox, ARCHlabs,
+# ArchStrike, XFerience, ArchMerge, Arch, Artix, Arya, Bedrock,
+# Bitrig, BlackArch, BLAG, BlankOn, BlueLight, bonsai, BSD,
+# BunsenLabs, Calculate, Carbs, CentOS, Chakra, ChaletOS,
+# Chapeau, Chrom*, Cleanjaro, ClearOS, Clear_Linux, Clover,
+# Condres, Container_Linux, CRUX, Cucumber, Debian, Deepin,
+# DesaOS, Devuan, DracOS, DarkOs, DragonFly, Drauger, Elementary,
+# EndeavourOS, Endless, EuroLinux, Exherbo, Fedora, Feren, FreeBSD,
+# FreeMiNT, Frugalware, Funtoo, GalliumOS, Garuda, Gentoo, Pentoo,
+# gNewSense, GNOME, GNU, GoboLinux, Grombyang, Guix, Haiku, Huayra,
+# Hyperbola, janus, Kali, KaOS, KDE_neon, Kibojoe, Kogaion,
+# Korora, KSLinux, Kubuntu, LEDE, LFS, Linux_Lite,
+# LMDE, Lubuntu, Lunar, macos, Mageia, MagpieOS, Mandriva,
+# Manjaro, Maui, Mer, Minix, LinuxMint, MX_Linux, Namib,
+# Neptune, NetBSD, Netrunner, Nitrux, NixOS, Nurunner,
+# NuTyX, OBRevenge, OpenBSD, openEuler, OpenIndiana, openmamba,
+# OpenMandriva, OpenStage, OpenWrt, osmc, Oracle, OS Elbrus, PacBSD,
+# Parabola, Pardus, Parrot, Parsix, TrueOS, PCLinuxOS, Peppermint,
+# popos, Porteus, PostMarketOS, Proxmox, Puppy, PureOS, Qubes, Radix,
+# Raspbian, Reborn_OS, Redstar, Redcore, Redhat, Refracted_Devuan,
+# Regata, Rosa, sabotage, Sabayon, Sailfish, SalentOS, Scientific,
+# Septor, SereneLinux, SharkLinux, Siduction, Slackware, SliTaz,
+# SmartOS, Solus, Source_Mage, Sparky, Star, SteamOS, SunOS,
+# openSUSE_Leap, openSUSE_Tumbleweed, openSUSE, SwagArch, Tails,
+# Trisquel, Ubuntu-Budgie, Ubuntu-GNOME, Ubuntu-MATE, Ubuntu-Studio,
+# Ubuntu, Venom, Void, Obarun, windows10, Windows7, Xubuntu, Zorin,
+# and IRIX have ascii logos
+# NOTE: Arch, Ubuntu, Redhat, and Dragonfly have 'old' logo variants.
+# Use '{distro name}_old' to use the old logos.
+# NOTE: Ubuntu has flavor variants.
+# Change this to Lubuntu, Kubuntu, Xubuntu, Ubuntu-GNOME,
+# Ubuntu-Studio, Ubuntu-Mate or Ubuntu-Budgie to use the flavors.
+# NOTE: Arcolinux, Dragonfly, Fedora, Alpine, Arch, Ubuntu,
+# CRUX, Debian, Gentoo, FreeBSD, Mac, NixOS, OpenBSD, android,
+# Antrix, CentOS, Cleanjaro, ElementaryOS, GUIX, Hyperbola,
+# Manjaro, MXLinux, NetBSD, Parabola, POP_OS, PureOS,
+# Slackware, SunOS, LinuxLite, OpenSUSE, Raspbian,
+# postmarketOS, and Void have a smaller logo variant.
+# Use '{distro name}_small' to use the small variants.
+ascii_distro="auto"
+
+# Ascii Colors
+#
+# Default: 'distro'
+# Values: 'distro', 'num' 'num' 'num' 'num' 'num' 'num'
+# Flag: --ascii_colors
+#
+# Example:
+# ascii_colors=(distro) - Ascii is colored based on Distro colors.
+# ascii_colors=(4 6 1 8 8 6) - Ascii is colored using these colors.
+ascii_colors=(120 228 210 210 175 140)
+
+
+# Bold ascii logo
+# Whether or not to bold the ascii logo.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --ascii_bold
+ascii_bold="on"
+
+
+# Image Options
+
+
+# Image loop
+# Setting this to on will make neofetch redraw the image constantly until
+# Ctrl+C is pressed. This fixes display issues in some terminal emulators.
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --loop
+image_loop="off"
+
+# Thumbnail directory
+#
+# Default: '~/.cache/thumbnails/neofetch'
+# Values: 'dir'
+thumbnail_dir="${XDG_CACHE_HOME:-${HOME}/.cache}/thumbnails/neofetch"
+
+# Crop mode
+#
+# Default: 'normal'
+# Values: 'normal', 'fit', 'fill'
+# Flag: --crop_mode
+#
+# See this wiki page to learn about the fit and fill options.
+# https://github.com/dylanaraps/neofetch/wiki/What-is-Waifu-Crop%3F
+crop_mode="normal"
+
+# Crop offset
+# Note: Only affects 'normal' crop mode.
+#
+# Default: 'center'
+# Values: 'northwest', 'north', 'northeast', 'west', 'center'
+# 'east', 'southwest', 'south', 'southeast'
+# Flag: --crop_offset
+crop_offset="center"
+
+# Image size
+# The image is half the terminal width by default.
+#
+# Default: 'auto'
+# Values: 'auto', '00px', '00%', 'none'
+# Flags: --image_size
+# --size
+image_size="auto"
+
+# Gap between image and text
+#
+# Default: '3'
+# Values: 'num', '-num'
+# Flag: --gap
+gap=3
+
+# Image offsets
+# Only works with the w3m backend.
+#
+# Default: '0'
+# Values: 'px'
+# Flags: --xoffset
+# --yoffset
+yoffset=0
+xoffset=0
+
+# Image background color
+# Only works with the w3m backend.
+#
+# Default: ''
+# Values: 'color', 'blue'
+# Flag: --bg_color
+background_color=
+
+
+# Misc Options
+
+# Stdout mode
+# Turn off all colors and disables image backend (ASCII/Image).
+# Useful for piping into another command.
+# Default: 'off'
+# Values: 'on', 'off'
+stdout="off"
diff --git a/mac/.config/pam-gnupg b/mac/.config/pam-gnupg
new file mode 100644
index 0000000..a3150cf
--- /dev/null
+++ b/mac/.config/pam-gnupg
@@ -0,0 +1,2 @@
+FBAE9F51CA060B2A5E377DD75CD76D3C44BE0FD9
+CEA80B05ABA46C5DE584655EFD7D26E81A2DFF65
diff --git a/mac/.config/sketchybar/colors.sh b/mac/.config/sketchybar/colors.sh
new file mode 100644
index 0000000..cdbd908
--- /dev/null
+++ b/mac/.config/sketchybar/colors.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+
+### Sonokai
+# export BLACK=0xff181819 #181819
+# export WHITE=0xffe2e2e3 #e2e2e3
+# export RED=0xfffc5d7c #fc5d7c
+# export GREEN=0xff9ed072 #9ed072
+# export BLUE=0xff76cce0 #76cce0
+# export YELLOW=0xffe7c664 #e7c664
+# export ORANGE=0xfff39660 #f39660
+# export MAGENTA=0xffb39df3 #b39df3
+# export GREY=0xff7f8490 #7f8490
+# export BG0=0xff2c2e34 #2c2e34
+# export BG1=0xff363944 #363944
+# export BG2=0xff414550 #414550
+# export TRANSPARENT=0x00000000 #000000
+
+### Catppuccin
+export ROSEWATER=0xfff4dbd6 #f4dbd6
+export FLAMINGO=0xfff0c6c6 #f0c6c6
+export PINK=0XFff5bde6 #f5bde6
+export MAUVE=0Xffc6a0f6 #c6a0f6
+export RED=0XFFed8796 #ed8796
+export MAROON=0xffee99a0 #ee99a0
+export PEACH=0Xfff5a97f #f5a97f
+export YELLOW=0xffeed49f #eed49f
+export GREEN=0Xffa6da95 #a6da95
+export TEAL=0XFf8bd5ca #8bd5ca
+export SKY=0XFF91d7e3 #91d7e3
+export SAPPHIRE=0xff7dc4e4 #7dc4e4
+export BLUE=0XFf8aadf4 #8aadf4
+export LAVENDER=0xffb7bdf8 #b7bdf8
+export TEXT=0xffcad3f5 #cad3f5
+export SUBTEXT1=0xffb8c0e0 #b8c0e0
+export SUBTEXT0=0xffa5adcb #a5adcb
+export OVERLAY2=0xff939ab7 #939ab7
+export OVERLAY1=0xff8087a2 #8087a2
+export OVERLAY0=0xff6e738d #6e738d
+export SURFACE2=0xff5b6078 #5b6078
+export SURFACE1=0xff494d64 #494d64
+export SURFACE0=0xff363a4f #363a4f
+export BASE=0XFf24273a #24273a
+export MANTLE=0xff1e2030 #1e2030
+export CRUST=0Xff181926 #181926
+
+# Others
+export MAGENTA=0xffc6a0f6 #c6a0f6
+export ORANGE=0xfff5a97f #f5a97f
+export CYAN=0xff89DDFF #89DDFF
+export OSBLUE=0xff0259D1 #0259D1
+
+# Base Colors
+export BASE=0xff24273a #24273a
+export BASE_BLACK="181926" #181926
+export BASE_WHITE="eeeeee" #eeeeee
+export WHITE=0xffcad3f5 #cad3f5
+export LIGHT_GREY=0xffa6accd #a6accd
+export GREY=0xff939ab7 #939ab7
+export GREY_50=0x80676e95 #676e95
+export DARK_GREY=0xff292d3e #292d3e
+export BLACK=0xff181926 #181926
+export BG0=0xff1e1e2e #1e1e2e
+export BG1=0x603c3e4f #3c3e4f
+export BG2=0x60494d64 #494d64
+export TRANSPARENT=0x00000000 #000000
+
+O100=0xff # 100%
+O75=0xbf # 75%
+O50=0x80 # 50%
+O25=0x40 # 25%
+O10=0x1a # 10%
+
+export BLACK_75="$O75""$BASE_BLACK"
+export BLACK_50="$O50""$BASE_BLACK"
+export BLACK_25="$O25""$BASE_BLACK"
+export WHITE_75="$O75""$BASE_WHITE"
+export WHITE_50="$O50""$BASE_WHITE"
+export WHITE_25="$O25""$BASE_WHITE"
+export WHITE_10="$O10""$BASE_WHITE"
+
+# Text Colors
+export TEXT=0xffcad3f5 #cad3f5
+export SUBTEXT0=0xffb8c0e0 #b8c0e0
+export SUBTEXT1=0xffa5adcb #a5adcb
+export SURFACE0=0xff363a4f #363a4f
+export SURFACE1=0xff494d64 #494d64
+export SURFACE2=0xff5b6078 #5b6078
+export OVERLAY0=0xff6e738d #6e738d
+export OVERLAY1=0xff8087a2 #8087a2
+export OVERLAY2=0xff939ab7 #939ab7
+
+# General Bar Colors
+export BAR_COLOR=$BG0
+export BAR_BORDER_COLOR=$BG2
+export BACKGROUND_1=$BG1
+export BACKGROUND_2=$BG2
+export CONTRAST=0xff34324a #34324a
+export HIGHLIGHT=$TEAL
+export ICON_COLOR=$WHITE # Color of all icons
+export ICON_COLOR_INACTIVE=$GREY
+export LABEL_COLOR=$WHITE # Color of all labels
+export POPUP_BACKGROUND_COLOR=$BAR_COLOR
+export POPUP_BORDER_COLOR=$WHITE
+export SHADOW_COLOR=$BLACK
diff --git a/mac/.config/sketchybar/globalstyles.sh b/mac/.config/sketchybar/globalstyles.sh
new file mode 100644
index 0000000..71b94ac
--- /dev/null
+++ b/mac/.config/sketchybar/globalstyles.sh
@@ -0,0 +1,132 @@
+#!/bin/bash
+
+# Load defined icons
+source "$CONFIG_DIR/icons.sh"
+
+# Load defined colors
+source "$CONFIG_DIR/colors.sh"
+
+PADDINGS=8
+FONT="Hack Nerd Font"
+
+# Bar Appearance
+bar=(
+ color=$BAR_COLOR
+ sticky=on
+ height=28
+ padding_left=$PADDINGS
+ padding_right=$PADDINGS
+ corner_radius=0
+ blur_radius=0
+ border_width=2
+ border_color=$TRANSPARENT
+ background_color=$BAR_COLOR
+ shadow=off
+ position=bottom
+ padding_right=10
+ padding_left=10
+ # y_offset=-2
+ margin=-5
+ sticky=on
+ topmost=off # on/off/window
+)
+
+# Setting up default values
+defaults=(
+ updates=when_shown
+ icon.font="$FONT:Bold:10.0"
+ icon.color=$ICON_COLOR
+ icon.padding_left=$PADDINGS
+ icon.padding_right=$PADDINGS
+ label.font="$FONT:Semibold:13"
+ label.color=$LABEL_COLOR
+ label.padding_left=$PADDINGS
+ label.padding_right=$PADDINGS
+ padding_right=$PADDINGS
+ padding_left=$PADDINGS
+ background.color=$BAR_COLOR
+ background.height=24
+ background.corner_radius=3
+ background.border_width=1
+ popup.background.border_width=2
+ popup.background.corner_radius=9
+ popup.background.border_color=$POPUP_BORDER_COLOR
+ popup.background.color=$POPUP_BACKGROUND_COLOR
+ popup.blur_radius=20
+ popup.background.shadow.drawing=on
+ scroll_texts=on
+)
+
+bracket_defaults=(
+ background.height=24
+ background.color=$BAR_COLOR
+ blur_radius=32
+ background.corner_radius=$PADDINGS
+)
+
+icon_defaults=(
+ label.drawing=off
+)
+
+# Item Defaults
+item_defaults=(
+ background.color=$TRANSPARENT
+ background.padding_left=$(($PADDINGS / 2))
+ background.padding_right=$(($PADDINGS / 2))
+ icon.padding_left=2
+ icon.padding_right=$(($PADDINGS / 2))
+ icon.background.corner_radius=4
+ icon.background.height=24
+ icon.font="$FONT:Regular:12"
+ icon.color=$ICON_COLOR
+ icon.highlight_color=$HIGHLIGHT
+ label.font="$FONT:Regular:12"
+ label.color=$LABEL_COLOR
+ label.highlight_color=$HIGHLIGHT
+ label.padding_left=$(($PADDINGS / 2))
+ updates=when_shown
+ scroll_texts=on
+)
+
+menu_defaults=(
+ popup.background.border_color=$POPUP_BORDER_COLOR
+ popup.background.color=$POPUP_BACKGROUND_COLOR
+ popup.background.shadow.drawing=on
+ popup.blur_radius=32
+ popup.background.corner_radius=$PADDINGS
+ popup.background.shadow.drawing=on
+ popup.background.border_width=1
+)
+
+menu_item_defaults=(
+ label.font="$FONT:Regular:13"
+ padding_left=$PADDINGS
+ padding_right=$PADDINGS
+ icon.padding_left=0
+ icon.color=$HIGHLIGHT
+ background.color=$TRANSPARENT
+)
+
+notification_defaults=(
+ drawing=off
+ update_freq=120
+ updates=on
+ background.color=$WHITE_25
+ background.height=24
+ background.corner_radius=16
+ icon.font.size=10
+ icon.padding_left=$PADDINGS
+ icon.padding_right=0
+ icon.color=$BLACK_75
+ label.color=$BLACK_75
+ label.padding_right=$PADDINGS
+ label.font.size=11
+ label.font.style=Bold
+)
+
+separator=(
+ background.height=1
+ width=200
+ background.color=$WHITE_25
+ background.y_offset=-16
+)
diff --git a/mac/.config/sketchybar/helper/cpu.h b/mac/.config/sketchybar/helper/cpu.h
new file mode 100644
index 0000000..c350ae3
--- /dev/null
+++ b/mac/.config/sketchybar/helper/cpu.h
@@ -0,0 +1,122 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <mach/mach.h>
+#include <stdbool.h>
+#include <time.h>
+
+#define MAX_TOPPROC_LEN 28
+
+static const char TOPPROC[] = { "/bin/ps -Aceo pid,pcpu,comm -r" };
+static const char FILTER_PATTERN[] = { "com.apple." };
+
+struct cpu {
+ host_t host;
+ mach_msg_type_number_t count;
+ host_cpu_load_info_data_t load;
+ host_cpu_load_info_data_t prev_load;
+ bool has_prev_load;
+
+ char command[256];
+};
+
+static inline void cpu_init(struct cpu* cpu) {
+ cpu->host = mach_host_self();
+ cpu->count = HOST_CPU_LOAD_INFO_COUNT;
+ cpu->has_prev_load = false;
+ snprintf(cpu->command, 100, "");
+}
+
+static inline void cpu_update(struct cpu* cpu) {
+ kern_return_t error = host_statistics(cpu->host,
+ HOST_CPU_LOAD_INFO,
+ (host_info_t)&cpu->load,
+ &cpu->count );
+
+ if (error != KERN_SUCCESS) {
+ printf("Error: Could not read cpu host statistics.\n");
+ return;
+ }
+
+ if (cpu->has_prev_load) {
+ uint32_t delta_user = cpu->load.cpu_ticks[CPU_STATE_USER]
+ - cpu->prev_load.cpu_ticks[CPU_STATE_USER];
+
+ uint32_t delta_system = cpu->load.cpu_ticks[CPU_STATE_SYSTEM]
+ - cpu->prev_load.cpu_ticks[CPU_STATE_SYSTEM];
+
+ uint32_t delta_idle = cpu->load.cpu_ticks[CPU_STATE_IDLE]
+ - cpu->prev_load.cpu_ticks[CPU_STATE_IDLE];
+
+ double user_perc = (double)delta_user / (double)(delta_system
+ + delta_user
+ + delta_idle);
+
+ double sys_perc = (double)delta_system / (double)(delta_system
+ + delta_user
+ + delta_idle);
+
+ double total_perc = user_perc + sys_perc;
+
+ FILE* file;
+ char line[1024];
+
+ file = popen(TOPPROC, "r");
+ if (!file) {
+ printf("Error: TOPPROC command errored out...\n" );
+ return;
+ }
+
+ fgets(line, sizeof(line), file);
+ fgets(line, sizeof(line), file);
+
+ char* start = strstr(line, FILTER_PATTERN);
+ char topproc[MAX_TOPPROC_LEN + 4];
+ uint32_t caret = 0;
+ for (int i = 0; i < sizeof(line); i++) {
+ if (start && i == start - line) {
+ i+=9;
+ continue;
+ }
+
+ if (caret >= MAX_TOPPROC_LEN && caret <= MAX_TOPPROC_LEN + 2) {
+ topproc[caret++] = '.';
+ continue;
+ }
+ if (caret > MAX_TOPPROC_LEN + 2) break;
+ topproc[caret++] = line[i];
+ if (line[i] == '\0') break;
+ }
+
+ topproc[MAX_TOPPROC_LEN + 3] = '\0';
+
+ pclose(file);
+
+ char color[16];
+ if (total_perc >= .7) {
+ snprintf(color, 16, "%s", getenv("RED"));
+ } else if (total_perc >= .3) {
+ snprintf(color, 16, "%s", getenv("ORANGE"));
+ } else if (total_perc >= .1) {
+ snprintf(color, 16, "%s", getenv("YELLOW"));
+ } else {
+ snprintf(color, 16, "%s", getenv("LABEL_COLOR"));
+ }
+
+ snprintf(cpu->command, 256, "--push cpu.sys %.2f "
+ "--push cpu.user %.2f "
+ "--set cpu.top label='%s' "
+ "--set cpu.percent label=%.0f%% label.color=%s ",
+ sys_perc,
+ user_perc,
+ topproc,
+ total_perc*100.,
+ color );
+ }
+ else {
+ snprintf(cpu->command, 256, "");
+ }
+
+ cpu->prev_load = cpu->load;
+ cpu->has_prev_load = true;
+}
diff --git a/mac/.config/sketchybar/helper/helper b/mac/.config/sketchybar/helper/helper
new file mode 100644
index 0000000..050cca7
--- /dev/null
+++ b/mac/.config/sketchybar/helper/helper
Binary files differ
diff --git a/mac/.config/sketchybar/helper/helper.c b/mac/.config/sketchybar/helper/helper.c
new file mode 100644
index 0000000..71c3038
--- /dev/null
+++ b/mac/.config/sketchybar/helper/helper.c
@@ -0,0 +1,31 @@
+#include "cpu.h"
+#include "sketchybar.h"
+
+struct cpu g_cpu;
+
+void handler(env env) {
+ // Environment variables passed from sketchybar can be accessed as seen below
+ char* name = env_get_value_for_key(env, "NAME");
+ char* sender = env_get_value_for_key(env, "SENDER");
+ char* info = env_get_value_for_key(env, "INFO");
+ char* selected = env_get_value_for_key(env, "SELECTED");
+
+ if ((strcmp(name, "cpu.percent") == 0)) {
+ // CPU graph updates
+ cpu_update(&g_cpu);
+
+ if (strlen(g_cpu.command) > 0) sketchybar(g_cpu.command);
+ }
+}
+
+int main (int argc, char** argv) {
+ cpu_init(&g_cpu);
+
+ if (argc < 2) {
+ printf("Usage: helper \"<bootstrap name>\"\n");
+ exit(1);
+ }
+
+ event_server_begin(handler, argv[1]);
+ return 0;
+}
diff --git a/mac/.config/sketchybar/helper/makefile b/mac/.config/sketchybar/helper/makefile
new file mode 100644
index 0000000..ac8721a
--- /dev/null
+++ b/mac/.config/sketchybar/helper/makefile
@@ -0,0 +1,3 @@
+
+helper: helper.c cpu.h sketchybar.h
+ clang -std=c99 -O3 helper.c -o helper
diff --git a/mac/.config/sketchybar/helper/sketchybar.h b/mac/.config/sketchybar/helper/sketchybar.h
new file mode 100644
index 0000000..2ab4c39
--- /dev/null
+++ b/mac/.config/sketchybar/helper/sketchybar.h
@@ -0,0 +1,209 @@
+#pragma once
+
+#include <mach/mach.h>
+#include <mach/mach_port.h>
+#include <mach/message.h>
+#include <bootstrap.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h>
+
+typedef char* env;
+
+#define MACH_HANDLER(name) void name(env env)
+typedef MACH_HANDLER(mach_handler);
+
+struct mach_message {
+ mach_msg_header_t header;
+ mach_msg_size_t msgh_descriptor_count;
+ mach_msg_ool_descriptor_t descriptor;
+};
+
+struct mach_buffer {
+ struct mach_message message;
+ mach_msg_trailer_t trailer;
+};
+
+struct mach_server {
+ bool is_running;
+ mach_port_name_t task;
+ mach_port_t port;
+ mach_port_t bs_port;
+
+ pthread_t thread;
+ mach_handler* handler;
+};
+
+static struct mach_server g_mach_server;
+static mach_port_t g_mach_port = 0;
+
+static inline char* env_get_value_for_key(env env, char* key) {
+ uint32_t caret = 0;
+ for(;;) {
+ if (!env[caret]) break;
+ if (strcmp(&env[caret], key) == 0)
+ return &env[caret + strlen(&env[caret]) + 1];
+
+ caret += strlen(&env[caret])
+ + strlen(&env[caret + strlen(&env[caret]) + 1])
+ + 2;
+ }
+ return (char*)"";
+}
+
+static inline mach_port_t mach_get_bs_port() {
+ mach_port_name_t task = mach_task_self();
+
+ mach_port_t bs_port;
+ if (task_get_special_port(task,
+ TASK_BOOTSTRAP_PORT,
+ &bs_port ) != KERN_SUCCESS) {
+ return 0;
+ }
+
+ mach_port_t port;
+ if (bootstrap_look_up(bs_port,
+ "git.felix.sketchybar",
+ &port ) != KERN_SUCCESS) {
+ return 0;
+ }
+
+ return port;
+}
+
+static inline void mach_receive_message(mach_port_t port, struct mach_buffer* buffer, bool timeout) {
+ *buffer = (struct mach_buffer) { 0 };
+ mach_msg_return_t msg_return;
+ if (timeout)
+ msg_return = mach_msg(&buffer->message.header,
+ MACH_RCV_MSG | MACH_RCV_TIMEOUT,
+ 0,
+ sizeof(struct mach_buffer),
+ port,
+ 100,
+ MACH_PORT_NULL );
+ else
+ msg_return = mach_msg(&buffer->message.header,
+ MACH_RCV_MSG,
+ 0,
+ sizeof(struct mach_buffer),
+ port,
+ MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL );
+
+ if (msg_return != MACH_MSG_SUCCESS) {
+ buffer->message.descriptor.address = NULL;
+ }
+}
+
+static inline char* mach_send_message(mach_port_t port, char* message, uint32_t len) {
+ if (!message || !port) {
+ return NULL;
+ }
+
+ struct mach_message msg = { 0 };
+ msg.header.msgh_remote_port = port;
+ msg.header.msgh_local_port = 0;
+ msg.header.msgh_id = 0;
+ msg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND,
+ MACH_MSG_TYPE_MAKE_SEND,
+ 0,
+ MACH_MSGH_BITS_COMPLEX );
+
+ msg.header.msgh_size = sizeof(struct mach_message);
+ msg.msgh_descriptor_count = 1;
+ msg.descriptor.address = message;
+ msg.descriptor.size = len * sizeof(char);
+ msg.descriptor.copy = MACH_MSG_VIRTUAL_COPY;
+ msg.descriptor.deallocate = false;
+ msg.descriptor.type = MACH_MSG_OOL_DESCRIPTOR;
+
+ mach_msg(&msg.header,
+ MACH_SEND_MSG,
+ sizeof(struct mach_message),
+ 0,
+ MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL );
+
+ return NULL;
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+static inline bool mach_server_begin(struct mach_server* mach_server, mach_handler handler, char* bootstrap_name) {
+ mach_server->task = mach_task_self();
+
+ if (mach_port_allocate(mach_server->task,
+ MACH_PORT_RIGHT_RECEIVE,
+ &mach_server->port ) != KERN_SUCCESS) {
+ return false;
+ }
+
+ if (mach_port_insert_right(mach_server->task,
+ mach_server->port,
+ mach_server->port,
+ MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) {
+ return false;
+ }
+
+ if (task_get_special_port(mach_server->task,
+ TASK_BOOTSTRAP_PORT,
+ &mach_server->bs_port) != KERN_SUCCESS) {
+ return false;
+ }
+
+ if (bootstrap_register(mach_server->bs_port,
+ bootstrap_name,
+ mach_server->port ) != KERN_SUCCESS) {
+ return false;
+ }
+
+ mach_server->handler = handler;
+ mach_server->is_running = true;
+ struct mach_buffer buffer;
+ while (mach_server->is_running) {
+ mach_receive_message(mach_server->port, &buffer, false);
+ mach_server->handler((env)buffer.message.descriptor.address);
+ mach_msg_destroy(&buffer.message.header);
+ }
+
+ return true;
+}
+#pragma clang diagnostic pop
+
+static inline char* sketchybar(char* message) {
+ uint32_t message_length = strlen(message) + 1;
+ char formatted_message[message_length + 1];
+
+ char quote = '\0';
+ uint32_t caret = 0;
+ for (int i = 0; i < message_length; ++i) {
+ if (message[i] == '"' || message[i] == '\'') {
+ if (quote == message[i]) quote = '\0';
+ else quote = message[i];
+ continue;
+ }
+ formatted_message[caret] = message[i];
+ if (message[i] == ' ' && !quote) formatted_message[caret] = '\0';
+ caret++;
+ }
+
+ if (caret > 0 && formatted_message[caret] == '\0'
+ && formatted_message[caret - 1] == '\0') {
+ caret--;
+ }
+
+ formatted_message[caret] = '\0';
+ if (!g_mach_port) g_mach_port = mach_get_bs_port();
+ char* response = mach_send_message(g_mach_port,
+ formatted_message,
+ caret + 1 );
+
+ if (response) return response;
+ else return (char*)"";
+}
+
+static inline void event_server_begin(mach_handler event_handler, char* bootstrap_name) {
+ mach_server_begin(&g_mach_server, event_handler, bootstrap_name);
+}
diff --git a/mac/.config/sketchybar/icons.sh b/mac/.config/sketchybar/icons.sh
new file mode 100644
index 0000000..2321507
--- /dev/null
+++ b/mac/.config/sketchybar/icons.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+# General Icons
+LOADING=􀖇
+APPLE=􀣺
+PREFERENCES=􀺽
+ACTIVITY=􀒓
+LOCK=􀒳
+BELL=􀋚
+BELL_DOT=􀝗
+
+# Git Icons
+GIT_ISSUE=􀍷
+GIT_DISCUSSION=􀒤
+GIT_PULL_REQUEST=􀙡
+GIT_COMMIT=􀡚
+GIT_INDICATOR=􀂓
+
+# Music
+MUSIC=􀑪
+
+# Spotify Icons
+SPOTIFY_BACK=􀊎
+SPOTIFY_PLAY_PAUSE=􀊈
+SPOTIFY_NEXT=􀊐
+SPOTIFY_SHUFFLE=􀊝
+SPOTIFY_REPEAT=􀊞
+
+# Yabai Icons
+YABAI_STACK=􀏭
+YABAI_FULLSCREEN_ZOOM=􀂓
+YABAI_PARENT_ZOOM=􀥃
+YABAI_FLOAT=􀢌
+YABAI_GRID=􀧍
+YABAI_SPLIT_VERTICAL=􀘜
+YABAI_SPLIT_HORIZONTAL=􀧋
+
+# Battery Icons
+BATTERY=
+BATTERY_100=􀛨
+BATTERY_75=􀺸
+BATTERY_50=􀺶
+BATTERY_25=􀛩
+BATTERY_0=􀛪
+BATTERY_CHARGING=􀢋
+
+CPU=
+DISK=󱛟
+MEMORY=﬙
+NETWORK=󰩠
+NETWORK_UP=󰍠
+NETWORK_DOWN=󰍝
+
+# Volume Icons
+VOLUME_100=􀊩
+VOLUME_66=􀊧
+VOLUME_33=􀊥
+VOLUME_10=􀊡
+VOLUME_0=􀊣
+
+# WiFi
+WIFI_CONNECTED=􀙇
+WIFI_DISCONNECTED=􀙈
+
+# svim
+MODE_NORMAL=􀂯
+MODE_INSERT=􀂥
+MODE_VISUAL=􀂿
+MODE_CMD=􀂙
+MODE_PENDING=􀈏
diff --git a/mac/.config/sketchybar/items/apple.sh b/mac/.config/sketchybar/items/apple.sh
new file mode 100644
index 0000000..5f4e9dc
--- /dev/null
+++ b/mac/.config/sketchybar/items/apple.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+POPUP_OFF='sketchybar --set apple.logo popup.drawing=off'
+POPUP_CLICK_SCRIPT='sketchybar --set $NAME popup.drawing=toggle'
+
+apple_logo=(
+ icon=$APPLE
+ icon.font="$FONT:Black:16.0"
+ icon.color=$BLUE
+ padding_right=-2
+ label.drawing=off
+ click_script="$POPUP_CLICK_SCRIPT"
+ popup.height=35
+)
+
+apple_prefs=(
+ icon=$PREFERENCES
+ label="Preferences"
+ click_script="open -a 'System Preferences'; $POPUP_OFF"
+)
+
+apple_activity=(
+ icon=$ACTIVITY
+ label="Activity"
+ click_script="open -a 'Activity Monitor'; $POPUP_OFF"
+)
+
+apple_lock=(
+ icon=$LOCK
+ label="Lock Screen"
+ click_script="pmset displaysleepnow; $POPUP_OFF"
+)
+
+sketchybar --add item apple.logo left \
+ --set apple.logo "${apple_logo[@]}" \
+ \
+ --add item apple.prefs popup.apple.logo \
+ --set apple.prefs "${apple_prefs[@]}" \
+ \
+ --add item apple.activity popup.apple.logo \
+ --set apple.activity "${apple_activity[@]}" \
+ \
+ --add item apple.lock popup.apple.logo \
+ --set apple.lock "${apple_lock[@]}"
diff --git a/mac/.config/sketchybar/items/battery.sh b/mac/.config/sketchybar/items/battery.sh
new file mode 100644
index 0000000..758547b
--- /dev/null
+++ b/mac/.config/sketchybar/items/battery.sh
@@ -0,0 +1,23 @@
+#!/bin/env/bash
+
+battery=(
+ "${menu_defaults[@]}"
+ icon.font.size=16
+ icon.font.style="Light"
+ label.drawing=off
+ update_freq=60
+ popup.align=right
+ click_script="sketchybar --set battery popup.drawing=toggle"
+ script="$PLUGIN_DIR/battery.sh"
+ updates=when_shown
+)
+
+sketchybar \
+ --add item battery right \
+ --set battery "${battery[@]}" \
+ --subscribe battery power_source_change \
+ mouse.entered \
+ mouse.exited \
+ mouse.exited.global \
+ --add item battery.details popup.battery \
+ --set battery.details "${menu_item_defaults[@]}" icon.drawing=off label.padding_left=0
diff --git a/mac/.config/sketchybar/items/brew.sh b/mac/.config/sketchybar/items/brew.sh
new file mode 100644
index 0000000..4e58efd
--- /dev/null
+++ b/mac/.config/sketchybar/items/brew.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# Trigger the brew_udpate event when brew update or upgrade is run from cmdline
+# e.g. via function in .zshrc or fish function
+
+POPUP_CLICK_SCRIPT="sketchybar --set $NAME popup.drawing=toggle"
+
+brew=(
+ script="$PLUGIN_DIR/brew.sh"
+ click_script="$POPUP_CLICK_SCRIPT"
+ icon=􀐛
+ label=?
+ update_freq=30
+ popup.align=right
+ popup.height=20
+ icon.padding_right=-1
+ padding_left=0
+ padding_right=-1
+ y_offset=1
+)
+
+brew_details=(
+ background.corner_radius=12
+ background.padding_left=5
+ background.padding_right=10
+ click_script="sketchybar --set brew popup.drawing=off"
+)
+
+sketchybar --add event brew_update \
+ --add item brew right \
+ --set brew "${brew[@]}" \
+ \
+ --subscribe brew brew_update \
+ mouse.entered \
+ mouse.exited \
+ mouse.exited.global \
+ \
+ --add item brew.details popup.brew \
+ --set brew.details "${brew_details[@]}"
diff --git a/mac/.config/sketchybar/items/cpu.sh b/mac/.config/sketchybar/items/cpu.sh
new file mode 100644
index 0000000..2a61f2f
--- /dev/null
+++ b/mac/.config/sketchybar/items/cpu.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+
+# cpu_top=(
+# label.font="$FONT:Semibold:7"
+# label=CPU
+# icon.drawing=off
+# width=0
+# padding_right=10
+# y_offset=6
+# )
+#
+# cpu_percent=(
+# label.font="$FONT:Heavy:12"
+# label=CPU
+# y_offset=-4
+# padding_right=10
+# width=55
+# icon.drawing=off
+# update_freq=4
+# mach_helper="$HELPER"
+# )
+#
+# cpu_sys=(
+# width=0
+# graph.color=$RED
+# graph.fill_color=$RED
+# label.drawing=off
+# icon.drawing=off
+# background.height=30
+# background.drawing=on
+# background.color=$TRANSPARENT
+# y_offset=3
+# )
+#
+# cpu_user=(
+# graph.color=$BLUE
+# label.drawing=off
+# icon.drawing=off
+# background.height=30
+# background.drawing=on
+# background.color=$TRANSPARENT
+# background.padding_left=10
+# y_offset=3
+# )
+#
+# sketchybar --add item cpu.top right \
+# --set cpu.top "${cpu_top[@]}" \
+# \
+# --add item cpu.percent right \
+# --set cpu.percent "${cpu_percent[@]}" \
+# \
+# --add graph cpu.sys right 75 \
+# --set cpu.sys "${cpu_sys[@]}" \
+# \
+# --add graph cpu.user right 75 \
+# --set cpu.user "${cpu_user[@]}"
+
+source "$HOME/.config/sketchybar/colors.sh"
+
+cpu_percent=(
+ label.font="$FONT:Heavy:12"
+ label=CPU%
+ label.color="$TEXT"
+ icon="$CPU"
+ icon.font.size=16
+ icon.color="$BLUE"
+ icon.padding_right=-2
+ padding_right=-2
+ update_freq=2
+ mach_helper="$HELPER"
+)
+
+sketchybar --add item cpu.percent right \
+ --set cpu.percent "${cpu_percent[@]}"
diff --git a/mac/.config/sketchybar/items/datetime.sh b/mac/.config/sketchybar/items/datetime.sh
new file mode 100644
index 0000000..4160a37
--- /dev/null
+++ b/mac/.config/sketchybar/items/datetime.sh
@@ -0,0 +1,37 @@
+#!/bin/env/bash
+
+sketchybar \
+ --add item date right \
+ --set date update_freq=60 \
+ label.font="$FONT:Semibold:10" \
+ align=right \
+ icon.drawing=off \
+ label.padding_right=4 \
+ \
+ width=0 \
+ script='sketchybar --set $NAME label="$(date "+%a, %b %d")"' \
+ click_script="open -a Calendar.app" \
+ --subscribe date system_woke \
+ mouse.entered \
+ mouse.exited \
+ mouse.exited.global \
+ --add item date.details popup.date \
+ --set date.details "${menu_item_defaults[@]}" \
+ --add item clock right \
+ --set clock update_freq=10 \
+ \
+ icon.drawing=off \
+ label.font="$FONT:Bold:11" \
+ align=right \
+ label.padding_right=4 \
+ popup.align=right \
+ "${menu_defaults[@]}" \
+ script="$PLUGIN_DIR/nextevent.sh" \
+ click_script="sketchybar --set clock popup.drawing=toggle; open -a Calendar.app" \
+ --subscribe clock system_woke \
+ mouse.entered \
+ mouse.exited \
+ mouse.exited.global \
+ --add item clock.details popup.clock \
+ --set clock.details "${menu_item_defaults[@]}" icon.drawing=off label.padding_left=0 # y_offset=3 \
+# y_offset=-6 \
diff --git a/mac/.config/sketchybar/items/disk.sh b/mac/.config/sketchybar/items/disk.sh
new file mode 100644
index 0000000..399427f
--- /dev/null
+++ b/mac/.config/sketchybar/items/disk.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/colors.sh" # Loads all defined colors
+source "$CONFIG_DIR/icons.sh" # Loads all defined icons
+
+disk=(
+ label.font="$FONT:Heavy:12"
+ label.color="$TEXT"
+ icon="$DISK"
+ icon.font="$FONT:Bold:18.0"
+ icon.padding_right=-2
+ icon.color="$MAROON"
+ update_freq=60
+ script="$PLUGIN_DIR/disk.sh"
+)
+
+sketchybar --add item disk right \
+ --set disk "${disk[@]}"
diff --git a/mac/.config/sketchybar/items/dnd.sh b/mac/.config/sketchybar/items/dnd.sh
new file mode 100644
index 0000000..84eaf8c
--- /dev/null
+++ b/mac/.config/sketchybar/items/dnd.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+sketchybar \
+ --add item dnd right \
+ --set dnd script="$PLUGIN_DIR/dnd.sh" \
+ label.drawing=off \
+ --add event focus_on "_NSDoNotDisturbEnabledNotification" \
+ --add event focus_off "_NSDoNotDisturbDisabledNotification" \
+ --subscribe dnd focus_on focus_off mouse.clicked
diff --git a/mac/.config/sketchybar/items/front_app.sh b/mac/.config/sketchybar/items/front_app.sh
new file mode 100644
index 0000000..5839243
--- /dev/null
+++ b/mac/.config/sketchybar/items/front_app.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+front_app=(
+ label.font="$FONT:Black:12.0"
+ icon.background.drawing=on
+ display=active
+ script="$PLUGIN_DIR/front_app.sh"
+ click_script="open -a 'Mission Control'"
+)
+
+sketchybar --add item front_app left \
+ --set front_app "${front_app[@]}" \
+ --subscribe front_app front_app_switched
diff --git a/mac/.config/sketchybar/items/github.sh b/mac/.config/sketchybar/items/github.sh
new file mode 100644
index 0000000..1a27fd5
--- /dev/null
+++ b/mac/.config/sketchybar/items/github.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+POPUP_CLICK_SCRIPT="sketchybar --set \$NAME popup.drawing=toggle"
+
+github_bell=(
+ padding_right=-5
+ update_freq=180
+ icon=$BELL
+ icon.font="$FONT:Bold:15.0"
+ icon.color=$BLUE
+ label=$LOADING
+ label.highlight_color=$BLUE
+ popup.align=right
+ script="$PLUGIN_DIR/github.sh"
+ click_script="$POPUP_CLICK_SCRIPT"
+)
+
+github_template=(
+ drawing=off
+ background.corner_radius=12
+ padding_left=7
+ padding_right=7
+ icon.background.height=2
+ icon.background.y_offset=-12
+)
+
+sketchybar --add event github.update \
+ --add item github.bell right \
+ --set github.bell "${github_bell[@]}" \
+ --subscribe github.bell mouse.entered \
+ mouse.exited \
+ mouse.exited.global \
+ system_woke \
+ github.update \
+ \
+ --add item github.template popup.github.bell \
+ --set github.template "${github_template[@]}"
diff --git a/mac/.config/sketchybar/items/kakaotalk.sh b/mac/.config/sketchybar/items/kakaotalk.sh
new file mode 100644
index 0000000..e5c5cf5
--- /dev/null
+++ b/mac/.config/sketchybar/items/kakaotalk.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+kakaotalk=(
+ "${notification_defaults[@]}"
+ icon=󰅺
+ icon.font.size=13
+ background.color=$YELLOW
+ script="$PLUGIN_DIR/kakaotalk.sh"
+ click_script="open -a /System/Applications/KakaoTalk.app"
+ icon.padding_left=7
+ icon.padding_right=2
+ label.padding_right=7
+ background.padding_right=5
+ background.height=20
+)
+
+sketchybar --add item kakaotalk right \
+ --set kakaotalk "${kakaotalk[@]}"
diff --git a/mac/.config/sketchybar/items/keyboard.sh b/mac/.config/sketchybar/items/keyboard.sh
new file mode 100644
index 0000000..bffaf52
--- /dev/null
+++ b/mac/.config/sketchybar/items/keyboard.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+keyboard=(
+ padding_right=4
+ icon.drawing=off
+ script="$PLUGIN_DIR/keyboard.sh"
+ icon.color=$GREY
+ icon.font="$FONT:Regular:14.0"
+)
+
+sketchybar --add item keyboard right \
+ --set keyboard "${keyboard[@]}" \
+ --add event keyboard_change "AppleSelectedInputSourcesChangedNotification" \
+ --subscribe keyboard keyboard_change
diff --git a/mac/.config/sketchybar/items/mail.sh b/mac/.config/sketchybar/items/mail.sh
new file mode 100644
index 0000000..590d52b
--- /dev/null
+++ b/mac/.config/sketchybar/items/mail.sh
@@ -0,0 +1,19 @@
+#!/bin/env/bash
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+mail=(
+ "${notification_defaults[@]}"
+ icon=􀍕
+ icon.y_offset=1
+ background.color=$YELLOW
+ background.height=18
+ icon.padding_left=6
+ label.padding_right=6
+ script="$PLUGIN_DIR/mail.sh"
+ click_script="open -a /System/Applications/Mail.app"
+)
+
+sketchybar --add item mail right \
+ --set mail "${mail[@]}"
diff --git a/mac/.config/sketchybar/items/memory.sh b/mac/.config/sketchybar/items/memory.sh
new file mode 100644
index 0000000..5ff38ca
--- /dev/null
+++ b/mac/.config/sketchybar/items/memory.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/colors.sh" # Loads all defined colors
+source "$CONFIG_DIR/icons.sh" # Loads all defined icons
+
+memory=(
+ label.font="$FONT:Heavy:12"
+ label.color="$TEXT"
+ icon="$MEMORY"
+ icon.font="$FONT:Bold:16.0"
+ icon.font.size=20
+ icon.color="$GREEN"
+ update_freq=15
+ script="$PLUGIN_DIR/memory.sh"
+ icon.padding_right=-2
+ padding_right=-2
+)
+
+sketchybar --add item memory right \
+ --set memory "${memory[@]}"
diff --git a/mac/.config/sketchybar/items/messages.sh b/mac/.config/sketchybar/items/messages.sh
new file mode 100644
index 0000000..9ad9513
--- /dev/null
+++ b/mac/.config/sketchybar/items/messages.sh
@@ -0,0 +1,13 @@
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+messages=(
+ "${notification_defaults[@]}"
+ icon=􀌤
+ background.color=$GREEN
+ script="$PLUGIN_DIR/messages.sh"
+ click_script="open -a /System/Applications/Messages.app"
+)
+
+sketchybar --add item messages right \
+ --set messages "${messages[@]}" \ No newline at end of file
diff --git a/mac/.config/sketchybar/items/mic.sh b/mac/.config/sketchybar/items/mic.sh
new file mode 100644
index 0000000..666af0e
--- /dev/null
+++ b/mac/.config/sketchybar/items/mic.sh
@@ -0,0 +1,5 @@
+sketchybar -m --add item mic right \
+ --set mic update_freq=3 \
+ --set mic script="$PLUGIN_DIR/mic.sh" \
+ --set mic click_script="$PLUGIN_DIR/mic_click.sh" \
+ padding_right=-8
diff --git a/mac/.config/sketchybar/items/music.sh b/mac/.config/sketchybar/items/music.sh
new file mode 100644
index 0000000..3730d88
--- /dev/null
+++ b/mac/.config/sketchybar/items/music.sh
@@ -0,0 +1,36 @@
+#!/bin/env/bash
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+music=(
+ "${bracket_defaults[@]}"
+ script="$PLUGIN_DIR/music.sh"
+ popup.align=center
+ padding_left=0
+ label.padding_right=$PADDINGS
+ padding_right=$(($PADDINGS * 2))
+ icon=􀊆
+ drawing=off
+ label="Loading…"
+ background.image=media.artwork
+ background.image.scale=0.75
+ background.image.corner_radius=$PADDINGS
+ icon.padding_left=48
+ label.max_chars=33
+ updates=on
+ --subscribe music media_change
+ --subscribe music mouse.entered
+ mouse.clicked
+ mouse.exited
+ mouse.exited.global
+)
+
+sketchybar \
+ --add item music center \
+ --set music "${music[@]}" \
+ --set music "${menu_defaults[@]}" \
+ --add item music.cover popup.music \
+ --add item music.artist popup.music \
+ --add item music.title popup.music \
+ --add item music.album popup.music
diff --git a/mac/.config/sketchybar/items/network.sh b/mac/.config/sketchybar/items/network.sh
new file mode 100644
index 0000000..93bf083
--- /dev/null
+++ b/mac/.config/sketchybar/items/network.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/globalstyles.sh"
+
+network_down=(
+ y_offset=-9
+ label.font="$FONT:Heavy:10"
+ label.color="$TEXT"
+ icon="$NETWORK_DOWN"
+ icon.font="$NERD_FONT:Bold:16.0"
+ icon.font.size=15
+ icon.color="$GREEN"
+ icon.highlight_color="$BLUE"
+ icon.padding_right=2
+ padding_right=-2
+ update_freq=1
+ icon.y_offset=1
+)
+
+network_up=(
+ background.padding_right=-65
+ y_offset=5
+ label.font="$FONT:Heavy:10"
+ label.color="$TEXT"
+ label.padding_right=5
+ icon="$NETWORK_UP"
+ icon.font="$NERD_FONT:Bold:16.0"
+ icon.font.size=15
+ icon.color="$GREEN"
+ icon.highlight_color="$BLUE"
+ icon.padding_right=2
+ icon.y_offset=1
+ update_freq=1
+ script="$PLUGIN_DIR/network.sh"
+)
+
+sketchybar --add item network.down right \
+ --set network.down "${network_down[@]}" \
+ --add item network.up right \
+ --set network.up "${network_up[@]}"
diff --git a/mac/.config/sketchybar/items/package_monitor.sh b/mac/.config/sketchybar/items/package_monitor.sh
new file mode 100644
index 0000000..89be3a2
--- /dev/null
+++ b/mac/.config/sketchybar/items/package_monitor.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Trigger the package_monitor_udpate event when package_monitor update or upgrade is run from cmdline
+# e.g. via function in .zshrc
+
+package_monitor=(
+ icon=􀐛
+ icon.font.size=12
+ icon.padding_right=-1
+ label=?
+ script="$PLUGIN_DIR/package_monitor.sh"
+ padding_left=-2
+)
+
+sketchybar --add event package_monitor_update \
+ --add item package_monitor right \
+ --set package_monitor "${package_monitor[@]}" \
+ --subscribe package_monitor package_monitor_update \
+ mouse.clicked
diff --git a/mac/.config/sketchybar/items/separator_right.sh b/mac/.config/sketchybar/items/separator_right.sh
new file mode 100644
index 0000000..13a6b7f
--- /dev/null
+++ b/mac/.config/sketchybar/items/separator_right.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+separator_right=(
+ icon=󰅁
+ icon.font="$FONT:Regular:25.0"
+ label.drawing=off
+ click_script='sketchybar --trigger toggle_stats'
+ icon.color="$TEXT"
+ padding_right=10
+)
+
+sketchybar --add item separator_right right \
+ --set separator_right "${separator_right[@]}"
diff --git a/mac/.config/sketchybar/items/spaces.sh b/mac/.config/sketchybar/items/spaces.sh
new file mode 100644
index 0000000..5dc28d0
--- /dev/null
+++ b/mac/.config/sketchybar/items/spaces.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/globalstyles.sh"
+
+SPACE_ICONS=("1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12")
+
+# Destroy space on right click, focus space on left click.
+# New space by left clicking separator (>)
+
+sid=0
+spaces=()
+for i in "${!SPACE_ICONS[@]}"; do
+ sid=$(($i + 1))
+
+ space=(
+ space=$sid
+ icon="${SPACE_ICONS[i]}"
+ icon.padding_left=2
+ icon.padding_right=-5
+ label.padding_right=10
+ label.font="sketchybar-app-font:Regular:16.0"
+ label.y_offset=-1
+ background.height=2
+ script="$PLUGIN_DIR/space.sh"
+ )
+
+ sketchybar --add space space.$sid left \
+ --set space.$sid "${space[@]}" \
+ --subscribe space.$sid mouse.clicked
+done
+
+spaces=(
+ background.color=$BACKGROUND_1
+ background.border_color=$BACKGROUND_2
+ background.border_width=2
+ background.drawing=on
+)
+
+space_creator=(
+ icon=󰅂
+ icon.font="$FONT:Heavy:25.0"
+ padding_left=2
+ padding_right=5
+ label.drawing=off
+ display=active
+ script="$PLUGIN_DIR/space_windows.sh"
+ associated_display=active
+ click_script='yabai -m space --create && sketchybar --trigger space_change'
+ icon.color=$WHITE
+)
+
+sketchybar --add bracket spaces '/space\..*/' \
+ --set spaces "${spaces[@]}" \
+ --add item space_creator left \
+ --set space_creator "${space_creator[@]}" \
+ --subscribe space_creator space_windows_change
diff --git a/mac/.config/sketchybar/items/spotify.sh b/mac/.config/sketchybar/items/spotify.sh
new file mode 100644
index 0000000..421e299
--- /dev/null
+++ b/mac/.config/sketchybar/items/spotify.sh
@@ -0,0 +1,201 @@
+#!/bin/bash
+
+SPOTIFY_EVENT="com.spotify.client.PlaybackStateChanged"
+POPUP_SCRIPT="sketchybar -m --set spotify.anchor popup.drawing=toggle"
+
+spotify_anchor=(
+ script="$PLUGIN_DIR/spotify.sh"
+ click_script="$POPUP_SCRIPT"
+ popup.horizontal=on
+ popup.align=center
+ popup.height=150
+ icon=􁁒
+ icon.font="$FONT:Regular:25.0"
+ label.drawing=off
+ drawing=off
+)
+
+spotify_cover=(
+ script="$PLUGIN_DIR/spotify.sh"
+ click_script="open -a 'Spotify'; $POPUP_SCRIPT"
+ label.drawing=off
+ icon.drawing=off
+ padding_left=12
+ padding_right=10
+ background.image.scale=0.2
+ background.image.drawing=on
+ background.drawing=on
+ background.image.corner_radius=9
+ shadow=on
+)
+
+spotify_title=(
+ icon.drawing=off
+ padding_left=0
+ padding_right=0
+ width=0
+ label.font="$FONT:Heavy:15.0"
+ label.max_chars=20
+ y_offset=55
+)
+
+spotify_artist=(
+ icon.drawing=off
+ y_offset=30
+ padding_left=0
+ padding_right=0
+ width=0
+ label.max_chars=20
+)
+
+spotify_album=(
+ icon.drawing=off
+ padding_left=0
+ padding_right=0
+ y_offset=15
+ width=0
+ label.max_chars=25
+)
+
+spotify_state=(
+ icon.drawing=on
+ icon.font="$FONT:Light Italic:10.0"
+ icon.width=35
+ icon="00:00"
+ label.drawing=on
+ label.font="$FONT:Light Italic:10.0"
+ label.width=35
+ label="00:00"
+ padding_left=0
+ padding_right=0
+ y_offset=-15
+ width=0
+ slider.background.height=6
+ slider.background.corner_radius=1
+ slider.background.color=$GREY
+ slider.highlight_color=$GREEN
+ slider.percentage=40
+ slider.width=115
+ script="$PLUGIN_DIR/spotify.sh"
+ update_freq=1
+ updates=when_shown
+)
+
+spotify_shuffle=(
+ icon=􀊝
+ icon.padding_left=5
+ icon.padding_right=5
+ icon.color=$BLACK
+ icon.highlight_color=$GREY
+ label.drawing=off
+ script="$PLUGIN_DIR/spotify.sh"
+ y_offset=-45
+)
+
+spotify_back=(
+ icon=􀊎
+ icon.padding_left=5
+ icon.padding_right=5
+ icon.color=$BLACK
+ script="$PLUGIN_DIR/spotify.sh"
+ label.drawing=off
+ y_offset=-45
+)
+
+spotify_play=(
+ icon=􀊔
+ background.height=40
+ background.corner_radius=20
+ width=40
+ align=center
+ background.color=$POPUP_BACKGROUND_COLOR
+ background.border_color=$WHITE
+ background.border_width=0
+ background.drawing=on
+ icon.padding_left=4
+ icon.padding_right=5
+ updates=on
+ label.drawing=off
+ script="$PLUGIN_DIR/spotify.sh"
+ y_offset=-45
+)
+
+spotify_next=(
+ icon=􀊐
+ icon.padding_left=5
+ icon.padding_right=5
+ icon.color=$BLACK
+ label.drawing=off
+ script="$PLUGIN_DIR/spotify.sh"
+ y_offset=-45
+)
+
+spotify_repeat=(
+ icon=􀊞
+ icon.highlight_color=$GREY
+ icon.padding_left=5
+ icon.padding_right=10
+ icon.color=$BLACK
+ label.drawing=off
+ script="$PLUGIN_DIR/spotify.sh"
+ y_offset=-45
+)
+
+spotify_controls=(
+ background.color=$GREEN
+ background.corner_radius=11
+ background.drawing=on
+ y_offset=-45
+)
+
+sketchybar --add event spotify_change $SPOTIFY_EVENT \
+ --add item spotify.anchor center \
+ --set spotify.anchor "${spotify_anchor[@]}" \
+ --subscribe spotify.anchor mouse.entered mouse.exited \
+ mouse.exited.global \
+ \
+ --add item spotify.cover popup.spotify.anchor \
+ --set spotify.cover "${spotify_cover[@]}" \
+ \
+ --add item spotify.title popup.spotify.anchor \
+ --set spotify.title "${spotify_title[@]}" \
+ \
+ --add item spotify.artist popup.spotify.anchor \
+ --set spotify.artist "${spotify_artist[@]}" \
+ \
+ --add item spotify.album popup.spotify.anchor \
+ --set spotify.album "${spotify_album[@]}" \
+ \
+ --add slider spotify.state popup.spotify.anchor \
+ --set spotify.state "${spotify_state[@]}" \
+ --subscribe spotify.state mouse.clicked \
+ \
+ --add item spotify.shuffle popup.spotify.anchor \
+ --set spotify.shuffle "${spotify_shuffle[@]}" \
+ --subscribe spotify.shuffle mouse.clicked \
+ \
+ --add item spotify.back popup.spotify.anchor \
+ --set spotify.back "${spotify_back[@]}" \
+ --subscribe spotify.back mouse.clicked \
+ \
+ --add item spotify.play popup.spotify.anchor \
+ --set spotify.play "${spotify_play[@]}" \
+ --subscribe spotify.play mouse.clicked spotify_change \
+ \
+ --add item spotify.next popup.spotify.anchor \
+ --set spotify.next "${spotify_next[@]}" \
+ --subscribe spotify.next mouse.clicked \
+ \
+ --add item spotify.repeat popup.spotify.anchor \
+ --set spotify.repeat "${spotify_repeat[@]}" \
+ --subscribe spotify.repeat mouse.clicked \
+ \
+ --add item spotify.spacer popup.spotify.anchor \
+ --set spotify.spacer width=5 \
+ \
+ --add bracket spotify.controls spotify.shuffle \
+ spotify.back \
+ spotify.play \
+ spotify.next \
+ spotify.repeat \
+ --set spotify.controls "${spotify_controls[@]}"
diff --git a/mac/.config/sketchybar/items/svim.sh b/mac/.config/sketchybar/items/svim.sh
new file mode 100644
index 0000000..502343d
--- /dev/null
+++ b/mac/.config/sketchybar/items/svim.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+svim=(
+ script="$PLUGIN_DIR/svim.sh"
+ icon=$INSERT_MODE
+ icon.font.size=20
+ updates=on
+ drawing=off
+)
+
+sketchybar --add event svim_update \
+ --add item svim right \
+ --set svim "${svim[@]}" \
+ --subscribe svim svim_update
diff --git a/mac/.config/sketchybar/items/system.sh b/mac/.config/sketchybar/items/system.sh
new file mode 100644
index 0000000..dc74f73
--- /dev/null
+++ b/mac/.config/sketchybar/items/system.sh
@@ -0,0 +1,53 @@
+### mem Widget ###
+sketchybar --add item mem right \
+ --set mem update_freq=10 \
+ icon="asdf" \
+ icon.font="Font Awesome 6 Free:Solid:15.4" \
+ icon.padding_right=4 \
+ icon.color=0xfff5c1e6 \
+ icon.y_offset=-3 \
+ label.y_offset=-3 \
+ label.font="$FONT:Medium:19.0" \
+ label.color=0xff47455c \
+ label.padding_right=8 \
+ background.drawing=on \
+ background.color=0xfff4f5f8 \
+ script="$PLUGIN_DIR/mem.sh"
+
+# ### cpu Widget ###
+# sketchybar --add item cpu right \
+# --set cpu update_freq=10 \
+# icon.font="Font Awesome 6 Free:Solid:15.4" \
+# icon.padding_right=4 \
+# icon.color=0xffedd6a4 \
+# icon.y_offset=-3 \
+# label.y_offset=-3 \
+# label.font="$FONT:Medium:19.0" \
+# label.color=0xff47455c \
+# label.padding_right=8 \
+# background.drawing=on \
+# background.color=0xfff4f5f8 \
+# script="$PLUGIN_DIR/cpu.sh" \
+
+### ssd Widget ###
+sketchybar --add item disk right \
+ --set disk update_freq=10 \
+ icon="asdf" \
+ icon.font="Font Awesome 6 Free:Solid:15.4" \
+ icon.padding_right=4 \
+ icon.color=0xfff37ea0 \
+ icon.y_offset=-3 \
+ label.y_offset=-3 \
+ label.font="$FONT:Medium:19.0" \
+ label.color=0xff47455c \
+ label.padding_right=8 \
+ background.drawing=on \
+ background.color=0xfff4f5f8 \
+ script="$PLUGIN_DIR/disk.sh"
+
+# sketchybar --add item collapse right \
+# --set collapse icon="" \
+# icon.font="Font Awesome 6 Free:Solid:20.0" \
+# icon.color=0xff47455c \
+# icon.y_offset=-3 \
+# click_script="$PLUGIN_DIR/collapse.sh"
diff --git a/mac/.config/sketchybar/items/thunderbird.sh b/mac/.config/sketchybar/items/thunderbird.sh
new file mode 100644
index 0000000..234b038
--- /dev/null
+++ b/mac/.config/sketchybar/items/thunderbird.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+thunderbird=(
+ "${notification_defaults[@]}"
+ icon=
+ icon.font.size=17
+ icon.color=$OSBLUE
+ icon.y_offset=0
+ background.color=$WHITE
+ script="$PLUGIN_DIR/thunderbird.sh"
+ click_script="open -a /System/Applications/Thunderbird.app"
+ icon.padding_left=7
+ icon.padding_right=-1
+ label.padding_right=7
+ background.padding_right=5
+ background.height=20
+)
+
+sketchybar --add item thunderbird right \
+ --set thunderbird "${thunderbird[@]}"
diff --git a/mac/.config/sketchybar/items/toggle_stats.sh b/mac/.config/sketchybar/items/toggle_stats.sh
new file mode 100644
index 0000000..301e0e4
--- /dev/null
+++ b/mac/.config/sketchybar/items/toggle_stats.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+sketchybar --add event hide_stats \
+ --add event show_stats \
+ --add event toggle_stats \
+ \
+ --add item animator right \
+ --set animator drawing=off \
+ updates=on \
+ script="$PLUGIN_DIR/toggle_stats.sh" \
+ --subscribe animator hide_stats show_stats toggle_stats
diff --git a/mac/.config/sketchybar/items/volume.sh b/mac/.config/sketchybar/items/volume.sh
new file mode 100644
index 0000000..a8efdd5
--- /dev/null
+++ b/mac/.config/sketchybar/items/volume.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+volume_slider=(
+ script="$PLUGIN_DIR/volume.sh"
+ updates=on
+ label.drawing=off
+ icon.drawing=off
+ slider.highlight_color=$BLUE
+ slider.background.height=5
+ slider.background.corner_radius=3
+ slider.background.color=$BACKGROUND_2
+ slider.knob=􀀁
+ slider.knob.drawing=on
+ padding_left=-5
+ padding_right=-1
+)
+
+volume_icon=(
+ click_script="$PLUGIN_DIR/volume_click.sh"
+ icon=$VOLUME_100
+ icon.width=0
+ icon.align=left
+ icon.font="$FONT:Regular:14.0"
+ label.width=25
+ label.align=left
+ label.font="$FONT:Regular:14.0"
+)
+
+sketchybar --add slider volume right \
+ --set volume "${volume_slider[@]}" \
+ --subscribe volume volume_change \
+ mouse.clicked \
+ \
+ --add item volume_icon right \
+ --set volume_icon "${volume_icon[@]}"
diff --git a/mac/.config/sketchybar/items/weather.sh b/mac/.config/sketchybar/items/weather.sh
new file mode 100644
index 0000000..287354d
--- /dev/null
+++ b/mac/.config/sketchybar/items/weather.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+weather=(
+ script="$PLUGIN_DIR/weather.sh"
+ "${menu_defaults[@]}"
+ popup.align=right
+ update_freq=300
+ updates=on
+ click_script="sketchybar --set $NAME popup.drawing=toggle; open -a /System/Applications/Weather.app"
+ --subscribe weather wifi_change
+ mouse.entered
+ mouse.exited
+ mouse.exited.global
+)
+
+sketchybar \
+ --add item weather right \
+ --set weather "${weather[@]}" \
+ --add item weather.details popup.weather \
+ --set weather.details "${menu_item_defaults[@]}" icon.drawing=off
diff --git a/mac/.config/sketchybar/items/wifi.sh b/mac/.config/sketchybar/items/wifi.sh
new file mode 100644
index 0000000..a903c23
--- /dev/null
+++ b/mac/.config/sketchybar/items/wifi.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+POPUP_OFF="sketchybar --set wifi popup.drawing=off"
+
+wifi=(
+ "${menu_defaults[@]}"
+ icon="$WIFI_DISCONNECTED"
+ script="$PLUGIN_DIR/wifi.sh"
+ label.drawing=off
+ click_script="$POPUP_CLICK_SCRIPT"
+ popup.align=right
+ updates=when_shown
+ update_freq=5
+ --subscribe wifi wifi_change
+ mouse.entered
+ mouse.exited
+ mouse.exited.global
+ icon.padding_left=-3
+)
+
+sketchybar \
+ --add item wifi right \
+ --set wifi "${wifi[@]}" \
+ --add item wifi.ssid popup.wifi \
+ --set wifi.ssid icon=􀅴 \
+ label="SSID" \
+ "${menu_item_defaults[@]}" \
+ click_script="open 'x-apple.systempreferences:com.apple.preference.network?Wi-Fi';$POPUP_OFF" \
+ --add item wifi.strength popup.wifi \
+ --set wifi.strength icon=􀋨 \
+ label="Speed" \
+ "${menu_item_defaults[@]}" \
+ click_script="open 'x-apple.systempreferences:com.apple.preference.network?Wi-Fi';$POPUP_OFF" \
+ --add item wifi.ipaddress popup.wifi \
+ --set wifi.ipaddress icon=􀆪 \
+ label="IP Address" \
+ "${menu_item_defaults[@]}" \
+ click_script="echo \"$IP_ADDRESS\"|pbcopy;$POPUP_OFF" \
+ --subscribe wifi wifi_change mouse.clicked
diff --git a/mac/.config/sketchybar/items/yabai.sh b/mac/.config/sketchybar/items/yabai.sh
new file mode 100644
index 0000000..c123a8b
--- /dev/null
+++ b/mac/.config/sketchybar/items/yabai.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+yabai=(
+ icon=$YABAI_GRID
+ label.drawing=off
+ script="$PLUGIN_DIR/yabai.sh"
+ icon.font="$FONT:Bold:14.0"
+ icon.color="$FLAMINGO"
+ padding_left=-2
+ padding_right=0
+)
+
+sketchybar --add event window_focus \
+ --add event windows_on_spaces \
+ --add item yabai left \
+ --set yabai "${yabai[@]}" \
+ --subscribe yabai window_focus \
+ space_change \
+ windows_on_spaces \
+ mouse.scrolled.global \
+ mouse.clicked
diff --git a/mac/.config/sketchybar/plugins/battery.sh b/mac/.config/sketchybar/plugins/battery.sh
new file mode 100644
index 0000000..60a8dc4
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/battery.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/colors.sh"
+
+render_item() {
+
+ PERCENTAGE=$(pmset -g batt | grep -Eo "\d+%" | cut -d% -f1)
+ CHARGING=$(pmset -g batt | grep 'AC Power')
+ CHARGING_STATUS="Not charging"
+
+ if [ $PERCENTAGE = "" ]; then
+ exit 0
+ fi
+
+ COLOR=$LABEL_COLOR
+ ICON="󰁹"
+
+ case ${PERCENTAGE} in
+ 9[0-9])
+ ICON="󰂂"
+ ;;
+ 8[0-9])
+ ICON="󰂁"
+ ;;
+ 7[0-9])
+ ICON="󰂀"
+ ;;
+ 6[0-9])
+ ICON="󰁿"
+ ;;
+ 5[0-9])
+ ICON="󰁾"
+ ;;
+ 4[0-9])
+ ICON="󰁽"
+ ;;
+ 3[0-9])
+ ICON="󰁼"
+ ;;
+ 2[0-9])
+ ICON="󰁻"
+ ;;
+ 1[0-9])
+ ICON="󰁺"
+ ;;
+ *)
+ ICON="󰂎"
+ COLOR=$RED
+ ;;
+ esac
+
+ if [[ $CHARGING != "" ]]; then
+ ICON="󰂄"
+ CHARGING_STATUS="Charging"
+ COLOR=$LABEL_COLOR
+ fi
+
+ sketchybar --set battery icon=$ICON
+
+}
+
+render_popup() {
+ sketchybar --set battery.details label="$PERCENTAGE% (${CHARGING_STATUS})"
+}
+
+update() {
+ render_item
+ render_popup
+}
+
+popup() {
+ sketchybar --set "$NAME" popup.drawing="$1"
+}
+
+case "$SENDER" in
+"routine" | "forced" | "power_source_change")
+ update
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/brew.sh b/mac/.config/sketchybar/plugins/brew.sh
new file mode 100644
index 0000000..308fd78
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/brew.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/globalstyles.sh"
+
+PREV_COUNT=$(sketchybar --query brew | jq -r .popup.items | grep ".package*" -c)
+
+render_bar_item() {
+ case "$COUNT" in
+ [3-5][0-9])
+ COLOR=$MAROON
+ ;;
+ [1-2][0-9])
+ COLOR=$PEACH
+ ;;
+ [1-9])
+ COLOR=$YELLOW
+ ;;
+ 0)
+ COLOR=$GREEN
+ COUNT=􀆅
+ ;;
+ esac
+
+ sketchybar --set "$NAME" label="$COUNT" label.color="$COLOR" icon.color="$COLOR"
+}
+
+add_outdated_header() {
+ brew_header=(
+ label="$(echo -e 'Outdated Brews')"
+ label.font="$FONT:Bold:14.0"
+ label.align=left
+ icon.drawing=off
+ click_script="sketchybar --set $NAME popup.drawing=off"
+ )
+
+ sketchybar --set brew.details "${brew_header[@]}"
+
+}
+
+render_popup() {
+ add_outdated_header
+
+ COUNTER=0
+ sketchybar --remove '/brew.package\.*/'
+
+ if [[ -n "$OUTDATED" ]]; then
+ while IFS= read -r package; do
+
+ brew_package=(
+ label="$package"
+ label.align=right
+ label.padding_left=20
+ icon.drawing=off
+ click_script="sketchybar --set $NAME popup.drawing=off"
+
+ )
+ item=brew.package."$COUNTER"
+
+ sketchybar --add item "$item" popup."$NAME" \
+ --set "$item" "${brew_package[@]}"
+
+ COUNTER=$((COUNTER + 1))
+
+ done <<<"$(echo -n "$OUTDATED" | grep '^')"
+ fi
+}
+
+update() {
+ brew update
+ COLOR=$RED
+ OUTDATED=$(brew outdated)
+ COUNT=$(echo -n "$OUTDATED" | grep -c '^')
+
+ render_bar_item
+ render_popup
+
+ if [ "$COUNT" -ne "$PREV_COUNT" ] 2>/dev/null || [ "$SENDER" = "forced" ]; then
+ sketchybar --animate tanh 15 --set "$NAME"
+ fi
+}
+
+popup() {
+ if [[ "$PREV_COUNT" -gt 0 ]]; then
+ sketchybar --set "$NAME" popup.drawing="$1"
+ else
+ sketchybar --set "$NAME" popup.drawing=off
+ fi
+
+}
+
+case "$SENDER" in
+"routine" | "forced")
+ update
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+"mouse.clicked")
+ popup toggle
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/disk.sh b/mac/.config/sketchybar/plugins/disk.sh
new file mode 100644
index 0000000..f2adc30
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/disk.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/globalstyles.sh"
+
+COUNT="$(df -H | grep -E '^(/dev/disk3s5)' | awk '{ printf ("%s\n", $5) }' | sed 's/%//')"
+
+COLOR=$RED
+
+case "$COUNT" in
+[7-8][0-9]) # 70-89%
+ COLOR=$PEACH
+ ;;
+[5-6][0-9]) # 50-69%
+ COLOR=$YELLOW
+ ;;
+[3-4][0-9]) # 20-49%
+ COLOR=$GREEN
+ ;;
+[1-2][0-9]) # 10-19%
+ COLOR=$LAVENDER
+ ;;
+[0-9]) # 0-9%
+ COLOR=$WHITE
+ ;;
+esac
+
+sketchybar -m --set "$NAME" \
+ label="$COUNT%" \
+ icon.color=$COLOR
diff --git a/mac/.config/sketchybar/plugins/dnd.sh b/mac/.config/sketchybar/plugins/dnd.sh
new file mode 100644
index 0000000..42c7ba6
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/dnd.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+update() {
+ dnd_enabled=$(cat ~/Library/DoNotDisturb/DB/Assertions.json | jq .data[0].storeAssertionRecords)
+ # alternate method: defaults read com.apple.controlcenter "NSStatusItem Visible FocusModes"
+ ICON=􀆺
+
+ if [ "$dnd_enabled" = "null" ]; then
+ COLOR=$WHITE_25
+ # echo $NAME: "Disabled"
+ else
+ COLOR=$WHITE
+ # echo $NAME: "Enabled"
+ fi
+
+ sketchybar --set $NAME icon=$ICON icon.color=$COLOR
+}
+
+toggle() {
+ osascript -e 'tell application "System Events" to keystroke "\\" using {control down, shift down, command down, option down}'
+}
+
+case "$SENDER" in
+"routine" | "forced" | "focus_on" | "focus_off")
+ update
+ ;;
+"mouse.clicked")
+ toggle
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/dndindicator.sh b/mac/.config/sketchybar/plugins/dndindicator.sh
new file mode 100644
index 0000000..0dd05e8
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/dndindicator.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# Thanks to reddit:rudypaul
+# BIN_DIR="/usr/bin"
+
+# PLUTIL="${BIN_DIR}/plutil"
+# XPATH="${BIN_DIR}/xpath"
+# BASE64="${BIN_DIR}/base64"
+
+dnd_enabled=$(
+defaults read com.apple.controlcenter "NSStatusItem Visible FocusModes"
+# dnd_enabled=$(
+# "${PLUTIL}" -extract dnd_prefs xml1 -o - ~/Library/Preferences/com.apple.ncprefs.plist |
+# "${XPATH}" -q -e 'string(//data)' |
+# "${BASE64}" -D |
+# "${PLUTIL}" -convert xml1 - -o - |
+# "${XPATH}" -q -e 'boolean(//key[text()="userPref"]/following-sibling::dict/key[text()="enabled"])'
+)
+
+if [ $dnd_enabled -eq 1 ]; then
+ sketchybar --set $NAME drawing=on icon=􀆺
+else
+ sketchybar --set $NAME drawing=off icon=
+fi \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/front_app.sh b/mac/.config/sketchybar/plugins/front_app.sh
new file mode 100644
index 0000000..066efb0
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/front_app.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ "$SENDER" = "front_app_switched" ]; then
+ # Set the app name and app icon and then animate a bounce for the icon size
+ sketchybar --set $NAME label="$INFO" icon.background.image="app.$INFO" \
+ --animate tanh 10 --set $NAME icon.background.image.scale=0.8 \
+ icon.background.image.scale=0.55
+fi
diff --git a/mac/.config/sketchybar/plugins/github.sh b/mac/.config/sketchybar/plugins/github.sh
new file mode 100644
index 0000000..bf09991
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/github.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+
+update() {
+ source "$CONFIG_DIR/colors.sh"
+ source "$CONFIG_DIR/icons.sh"
+
+ NOTIFICATIONS="$(gh api notifications)"
+ COUNT="$(echo "$NOTIFICATIONS" | jq 'length')"
+ args=()
+ if [ "$NOTIFICATIONS" = "[]" ]; then
+ args+=(--set $NAME icon=$BELL label="0")
+ else
+ args+=(--set $NAME icon=$BELL_DOT label="$COUNT")
+ fi
+
+ PREV_COUNT=$(sketchybar --query github.bell | jq -r .label.value)
+ # For sound to play around with:
+ # afplay /System/Library/Sounds/Morse.aiff
+
+ args+=(--remove '/github.notification\.*/')
+
+ COUNTER=0
+ COLOR=$BLUE
+ args+=(--set github.bell icon.color=$COLOR)
+
+ while read -r repo url type title; do
+ COUNTER=$((COUNTER + 1))
+ IMPORTANT="$(echo "$title" | egrep -i "(deprecat|break|broke)")"
+ COLOR=$BLUE
+ PADDING=0
+
+ if [ "${repo}" = "" ] && [ "${title}" = "" ]; then
+ repo="Note"
+ title="No new notifications"
+ fi
+ case "${type}" in
+ "'Issue'")
+ COLOR=$GREEN
+ ICON=$GIT_ISSUE
+ URL="$(gh api "$(echo "${url}" | sed -e "s/^'//" -e "s/'$//")" | jq .html_url)"
+ ;;
+ "'Discussion'")
+ COLOR=$WHITE
+ ICON=$GIT_DISCUSSION
+ URL="https://www.github.com/notifications"
+ ;;
+ "'PullRequest'")
+ COLOR=$MAGENTA
+ ICON=$GIT_PULL_REQUEST
+ URL="$(gh api "$(echo "${url}" | sed -e "s/^'//" -e "s/'$//")" | jq .html_url)"
+ ;;
+ "'Commit'")
+ COLOR=$WHITE
+ ICON=$GIT_COMMIT
+ URL="$(gh api "$(echo "${url}" | sed -e "s/^'//" -e "s/'$//")" | jq .html_url)"
+ ;;
+ esac
+
+ if [ "$IMPORTANT" != "" ]; then
+ COLOR=$RED
+ ICON=􀁞
+ args+=(--set github.bell icon.color=$COLOR)
+ fi
+
+ notification=(
+ label="$(echo "$title" | sed -e "s/^'//" -e "s/'$//")"
+ icon="$ICON $(echo "$repo" | sed -e "s/^'//" -e "s/'$//"):"
+ icon.padding_left="$PADDING"
+ label.padding_right="$PADDING"
+ icon.color=$COLOR
+ position=popup.github.bell
+ icon.background.color=$COLOR
+ drawing=on
+ click_script="open \"$URL\"; sketchybar --set github.bell popup.drawing=off; sleep 5; sketchybar --trigger github.update"
+ )
+
+ args+=(--clone github.notification.$COUNTER github.template
+ --set github.notification.$COUNTER "${notification[@]}")
+ done <<<"$(echo "$NOTIFICATIONS" | jq -r '.[] | [.repository.name, .subject.latest_comment_url, .subject.type, .subject.title] | @sh')"
+
+ sketchybar -m "${args[@]}" >/dev/null
+
+ if [ $COUNT -gt $PREV_COUNT ] 2>/dev/null || [ "$SENDER" = "forced" ]; then
+ sketchybar --animate tanh 15 --set github.bell label.y_offset=5 label.y_offset=0
+ fi
+}
+
+popup() {
+ sketchybar --set $NAME popup.drawing=$1
+}
+
+case "$SENDER" in
+"routine" | "forced" | "github.update")
+ update
+ ;;
+"system_woke")
+ sleep 10 && update # Wait for network to connect
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+"mouse.clicked")
+ popup toggle
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/icon_map.sh b/mac/.config/sketchybar/plugins/icon_map.sh
new file mode 100644
index 0000000..1b2f82a
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/icon_map.sh
@@ -0,0 +1,486 @@
+#!/bin/bash
+
+function icon_map() {
+ case "$1" in
+ "Typora")
+ icon_result=":text:"
+ ;;
+ "Orion" | "Orion RC")
+ icon_result=":orion:"
+ ;;
+ "Grammarly Editor")
+ icon_result=":grammarly:"
+ ;;
+ "kitty")
+ icon_result=":kitty:"
+ ;;
+ "ClickUp")
+ icon_result=":click_up:"
+ ;;
+ "Iris")
+ icon_result=":iris:"
+ ;;
+ "PomoDone App")
+ icon_result=":pomodone:"
+ ;;
+ "qutebrowser")
+ icon_result=":qute_browser:"
+ ;;
+ "Raindrop.io")
+ icon_result=":raindrop_io:"
+ ;;
+ "Airmail")
+ icon_result=":airmail:"
+ ;;
+ "Affinity Publisher 2")
+ icon_result=":affinity_publisher_2:"
+ ;;
+ "Calendar" | "日历" | "Fantastical" | "Cron" | "Amie")
+ icon_result=":calendar:"
+ ;;
+ "Figma")
+ icon_result=":figma:"
+ ;;
+ "Element")
+ icon_result=":element:"
+ ;;
+ "Signal")
+ icon_result=":signal:"
+ ;;
+ "Mattermost")
+ icon_result=":mattermost:"
+ ;;
+ "Caprine")
+ icon_result=":caprine:"
+ ;;
+ "Microsoft To Do" | "Things")
+ icon_result=":things:"
+ ;;
+ "Godot")
+ icon_result=":godot:"
+ ;;
+ "Android Messages")
+ icon_result=":android_messages:"
+ ;;
+ "Zed")
+ icon_result=":zed:"
+ ;;
+ "Anytype")
+ icon_result=":anytype:"
+ ;;
+ "TeamSpeak 3")
+ icon_result=":team_speak:"
+ ;;
+ "LibreWolf")
+ icon_result=":libre_wolf:"
+ ;;
+ "Neovide" | "neovide")
+ icon_result=":neovide:"
+ ;;
+ "Spotlight")
+ icon_result=":spotlight:"
+ ;;
+ "微信")
+ icon_result=":wechat:"
+ ;;
+ "Dropbox")
+ icon_result=":dropbox:"
+ ;;
+ "Transmit")
+ icon_result=":transmit:"
+ ;;
+ "TickTick")
+ icon_result=":tick_tick:"
+ ;;
+ "Parallels Desktop")
+ icon_result=":parallels:"
+ ;;
+ "Audacity")
+ icon_result=":audacity:"
+ ;;
+ "Rider" | "JetBrains Rider")
+ icon_result=":rider:"
+ ;;
+ "DEVONthink 3")
+ icon_result=":devonthink3:"
+ ;;
+ "Docker" | "Docker Desktop")
+ icon_result=":docker:"
+ ;;
+ "Matlab")
+ icon_result=":matlab:"
+ ;;
+ "VLC")
+ icon_result=":vlc:"
+ ;;
+ "Alacritty")
+ icon_result=":alacritty:"
+ ;;
+ "Pages" | "Pages 文稿")
+ icon_result=":pages:"
+ ;;
+ "Bear")
+ icon_result=":bear:"
+ ;;
+ "Pine")
+ icon_result=":pine:"
+ ;;
+ "Affinity Designer 2")
+ icon_result=":affinity_designer_2:"
+ ;;
+ "Keyboard Maestro")
+ icon_result=":keyboard_maestro:"
+ ;;
+ "Joplin")
+ icon_result=":joplin:"
+ ;;
+ "mpv")
+ icon_result=":mpv:"
+ ;;
+ "zoom.us")
+ icon_result=":zoom:"
+ ;;
+ "Affinity Photo 2")
+ icon_result=":affinity_photo_2:"
+ ;;
+ "Music" | "音乐")
+ icon_result=":music:"
+ ;;
+ "League of Legends")
+ icon_result=":league_of_legends:"
+ ;;
+ "Tor Browser")
+ icon_result=":tor_browser:"
+ ;;
+ "Hyper")
+ icon_result=":hyper:"
+ ;;
+ "‎WhatsApp")
+ icon_result=":whats_app:"
+ ;;
+ "카카오톡")
+ icon_result=":kakaotalk:"
+ ;;
+ "Discord" | "Discord Canary" | "Discord PTB")
+ icon_result=":discord:"
+ ;;
+ "Neovide" | "MacVim" | "Vim" | "VimR")
+ icon_result=":vim:"
+ ;;
+ "Keynote" | "Keynote 讲演")
+ icon_result=":keynote:"
+ ;;
+ "iTerm")
+ icon_result=":iterm:"
+ ;;
+ "IntelliJ IDEA")
+ icon_result=":idea:"
+ ;;
+ "Finder" | "访达")
+ icon_result=":finder:"
+ ;;
+ "Xcode")
+ icon_result=":xcode:"
+ ;;
+ "GoLand")
+ icon_result=":goland:"
+ ;;
+ "Android Studio")
+ icon_result=":android_studio:"
+ ;;
+ "MoneyMoney")
+ icon_result=":bank:"
+ ;;
+ "Spotify")
+ icon_result=":spotify:"
+ ;;
+ "KeePassXC")
+ icon_result=":kee_pass_x_c:"
+ ;;
+ "Alfred")
+ icon_result=":alfred:"
+ ;;
+ "Color Picker" | "数码测色计")
+ icon_result=":color_picker:"
+ ;;
+ "Microsoft Word")
+ icon_result=":microsoft_word:"
+ ;;
+ "Microsoft PowerPoint")
+ icon_result=":microsoft_power_point:"
+ ;;
+ "Notes" | "备忘录")
+ icon_result=":notes:"
+ ;;
+ "Microsoft Edge")
+ icon_result=":microsoft_edge:"
+ ;;
+ "Sublime Text")
+ icon_result=":sublime_text:"
+ ;;
+ "Sequel Ace")
+ icon_result=":sequel_ace:"
+ ;;
+ "Folx")
+ icon_result=":folx:"
+ ;;
+ "DingTalk" | "钉钉" | "阿里钉")
+ icon_result=":dingtalk:"
+ ;;
+ "WebStorm")
+ icon_result=":web_storm:"
+ ;;
+ "Sequel Pro")
+ icon_result=":sequel_pro:"
+ ;;
+ "Skype")
+ icon_result=":skype:"
+ ;;
+ "网易云音乐")
+ icon_result=":netease_music:"
+ ;;
+ "PyCharm")
+ icon_result=":pycharm:"
+ ;;
+ "Canary Mail" | "HEY" | "Mail" | "Mailspring" | "MailMate" | "邮件")
+ icon_result=":mail:"
+ ;;
+ "Default")
+ icon_result=":default:"
+ ;;
+ "App Store")
+ icon_result=":app_store:"
+ ;;
+ "Calibre")
+ icon_result=":book:"
+ ;;
+ "Todoist")
+ icon_result=":todoist:"
+ ;;
+ "Emacs")
+ icon_result=":emacs:"
+ ;;
+ "Messenger")
+ icon_result=":messenger:"
+ ;;
+ "Tower")
+ icon_result=":tower:"
+ ;;
+ "VSCodium")
+ icon_result=":vscodium:"
+ ;;
+ "Drafts")
+ icon_result=":drafts:"
+ ;;
+ "Cypress")
+ icon_result=":cypress:"
+ ;;
+ "GitHub Desktop")
+ icon_result=":git_hub:"
+ ;;
+ "Telegram")
+ icon_result=":telegram:"
+ ;;
+ "Firefox Developer Edition" | "Firefox Nightly")
+ icon_result=":firefox_developer_edition:"
+ ;;
+ "Min")
+ icon_result=":min_browser:"
+ ;;
+ "Sketch")
+ icon_result=":sketch:"
+ ;;
+ "Affinity Photo")
+ icon_result=":affinity_photo:"
+ ;;
+ "MAMP" | "MAMP PRO")
+ icon_result=":mamp:"
+ ;;
+ "Insomnia")
+ icon_result=":insomnia:"
+ ;;
+ "Bitwarden")
+ icon_result=":bit_warden:"
+ ;;
+ "Warp")
+ icon_result=":warp:"
+ ;;
+ "System Preferences" | "System Settings" | "系统设置")
+ icon_result=":gear:"
+ ;;
+ "Affinity Designer")
+ icon_result=":affinity_designer:"
+ ;;
+ "Live")
+ icon_result=":ableton:"
+ ;;
+ "Arc")
+ icon_result=":arc:"
+ ;;
+ "Chromium" | "Google Chrome" | "Google Chrome Canary")
+ icon_result=":google_chrome:"
+ ;;
+ "Jellyfin Media Player")
+ icon_result=":jellyfin:"
+ ;;
+ "Zulip")
+ icon_result=":zulip:"
+ ;;
+ "1Password")
+ icon_result=":one_password:"
+ ;;
+ "FaceTime" | "FaceTime 通话")
+ icon_result=":face_time:"
+ ;;
+ "Citrix Workspace" | "Citrix Viewer")
+ icon_result=":citrix:"
+ ;;
+ "Logseq")
+ icon_result=":logseq:"
+ ;;
+ "Reeder")
+ icon_result=":reeder5:"
+ ;;
+ "Code" | "Code - Insiders")
+ icon_result=":code:"
+ ;;
+ "Notion")
+ icon_result=":notion:"
+ ;;
+ "Final Cut Pro")
+ icon_result=":final_cut_pro:"
+ ;;
+ "Zotero")
+ icon_result=":zotero:"
+ ;;
+ "Safari" | "Safari浏览器" | "Safari Technology Preview")
+ icon_result=":safari:"
+ ;;
+ "Blender")
+ icon_result=":blender:"
+ ;;
+ "Affinity Publisher")
+ icon_result=":affinity_publisher:"
+ ;;
+ "Spark Desktop")
+ icon_result=":spark:"
+ ;;
+ "Zeplin")
+ icon_result=":zeplin:"
+ ;;
+ "Replit")
+ icon_result=":replit:"
+ ;;
+ "Podcasts" | "播客")
+ icon_result=":podcasts:"
+ ;;
+ "NordVPN")
+ icon_result=":nord_vpn:"
+ ;;
+ "Notability")
+ icon_result=":notability:"
+ ;;
+ "Numbers" | "Numbers 表格")
+ icon_result=":numbers:"
+ ;;
+ "Nova")
+ icon_result=":nova:"
+ ;;
+ "Microsoft Excel")
+ icon_result=":microsoft_excel:"
+ ;;
+ "Trello")
+ icon_result=":trello:"
+ ;;
+ "Pi-hole Remote")
+ icon_result=":pihole:"
+ ;;
+ "Linear")
+ icon_result=":linear:"
+ ;;
+ "CleanMyMac X")
+ icon_result=":desktop:"
+ ;;
+ "GrandTotal" | "Receipts")
+ icon_result=":dollar:"
+ ;;
+ "Evernote Legacy")
+ icon_result=":evernote_legacy:"
+ ;;
+ "OmniFocus")
+ icon_result=":omni_focus:"
+ ;;
+ "Terminal" | "终端")
+ icon_result=":terminal:"
+ ;;
+ "Atom")
+ icon_result=":atom:"
+ ;;
+ "Kakoune")
+ icon_result=":kakoune:"
+ ;;
+ "Reminders" | "提醒事项")
+ icon_result=":reminders:"
+ ;;
+ "Tana")
+ icon_result=":tana:"
+ ;;
+ "OBS")
+ icon_result=":obsstudio:"
+ ;;
+ "VMware Fusion")
+ icon_result=":vmware_fusion:"
+ ;;
+ "Tweetbot" | "Twitter")
+ icon_result=":twitter:"
+ ;;
+ "Microsoft Teams")
+ icon_result=":microsoft_teams:"
+ ;;
+ "Yuque" | "语雀")
+ icon_result=":yuque:"
+ ;;
+ "Slack")
+ icon_result=":slack:"
+ ;;
+ "Vivaldi")
+ icon_result=":vivaldi:"
+ ;;
+ "Setapp")
+ icon_result=":setapp:"
+ ;;
+ "TIDAL")
+ icon_result=":tidal:"
+ ;;
+ "Miro")
+ icon_result=":miro:"
+ ;;
+ "Messages" | "信息" | "Nachrichten")
+ icon_result=":messages:"
+ ;;
+ "Brave Browser")
+ icon_result=":brave_browser:"
+ ;;
+ "Preview" | "预览" | "Skim" | "zathura")
+ icon_result=":pdf:"
+ ;;
+ "Obsidian")
+ icon_result=":obsidian:"
+ ;;
+ "Thunderbird")
+ icon_result=":thunderbird:"
+ ;;
+ "Firefox")
+ icon_result=":firefox:"
+ ;;
+ "WezTerm")
+ icon_result=":wezterm:"
+ ;;
+ *)
+ icon_result=":default:"
+ ;;
+ esac
+}
+
+icon_map "$1"
+echo "$icon_result"
diff --git a/mac/.config/sketchybar/plugins/kakaotalk.sh b/mac/.config/sketchybar/plugins/kakaotalk.sh
new file mode 100644
index 0000000..e959621
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/kakaotalk.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+TEXT=$(lsappinfo info -only StatusLabel "KakaoTalk" | grep -o '"label"="[0-9]*"' | awk -F'"' '{print $4}')
+
+if [[ $TEXT -gt 0 ]]; then
+ sketchybar -m --set $NAME drawing=on label="$TEXT"
+else
+ sketchybar -m --set $NAME drawing=off
+fi
diff --git a/mac/.config/sketchybar/plugins/keyboard.sh b/mac/.config/sketchybar/plugins/keyboard.sh
new file mode 100644
index 0000000..c6b5681
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/keyboard.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# this is jank and ugly, I know
+LAYOUT="$(defaults read ~/Library/Preferences/com.apple.HIToolbox.plist AppleSelectedInputSources | grep "KeyboardLayout Name" | cut -c 33- | rev | cut -c 2- | rev)"
+
+# specify short layouts individually.
+case "$LAYOUT" in
+"\"Dvorak\"") SHORT_LAYOUT="DV" ;;
+"\"U.S.\"") SHORT_LAYOUT="US" ;;
+*) SHORT_LAYOUT="한" ;;
+esac
+
+sketchybar --set keyboard label="$SHORT_LAYOUT"
diff --git a/mac/.config/sketchybar/plugins/mail.sh b/mac/.config/sketchybar/plugins/mail.sh
new file mode 100644
index 0000000..6a74b07
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/mail.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+TEXT=$(lsappinfo info -only StatusLabel "Mail" | grep -o '"label"="[0-9]*"' | awk -F'"' '{print $4}')
+
+if [[ $TEXT -gt 0 ]]; then
+ sketchybar -m --set $NAME drawing=on label="$TEXT"
+else
+ sketchybar -m --set $NAME drawing=off
+fi \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/memory.sh b/mac/.config/sketchybar/plugins/memory.sh
new file mode 100644
index 0000000..6917458
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/memory.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/globalstyles.sh"
+
+# Get total physical memory in bytes
+total_memory=$(sysctl -n hw.memsize)
+
+# Get memory page size in bytes
+page_size=$(vm_stat | grep "page size of" | awk '{print $8}' | sed 's/\.$//') # Correctly strip the period at the end
+
+# Get various memory statistics from vm_stat
+vm_stat=$(vm_stat)
+pages_free=$(echo "$vm_stat" | grep "Pages free:" | awk '{print $3}' | sed 's/\.$//') # Remove dot at the end
+pages_active=$(echo "$vm_stat" | grep "Pages active:" | awk '{print $3}' | sed 's/\.$//')
+pages_inactive=$(echo "$vm_stat" | grep "Pages inactive:" | awk '{print $3}' | sed 's/\.$//')
+pages_speculative=$(echo "$vm_stat" | grep "Pages speculative:" | awk '{print $3}' | sed 's/\.$//')
+pages_wired_down=$(echo "$vm_stat" | grep "Pages wired down:" | awk '{print $4}' | sed 's/\.$//')
+compressed_pages=$(echo "$vm_stat" | grep "Pages occupied by compressor:" | awk '{print $5}' | sed 's/\.$//')
+
+# Calculate total used memory pages
+total_used_pages=$((pages_active + pages_wired_down + compressed_pages))
+
+# Convert pages to bytes
+total_used_memory_bytes=$((total_used_pages * page_size))
+
+# Calculate memory used percentage as an integer
+USAGE=$((total_used_memory_bytes * 100 / total_memory))
+COUNT="$(memory_pressure | grep "System-wide memory free percentage:" | awk '{ val = 100 - $5; if (val < 10) printf("%1.0f\n", val); else printf("%02.0f\n", val) }')"
+
+COLOR=$RED
+
+case "$COUNT" in
+[5-6][0-9]) # 50-69%
+ COLOR=$YELLOW
+ ;;
+[3-4][0-9]) # 20-49%
+ COLOR=$GREEN
+ ;;
+[1-2][0-9]) # 10-19%
+ COLOR=$LAVENDER
+ ;;
+[0-9]) # 0-9%
+ COLOR=$WHITE
+ ;;
+esac
+
+sketchybar -m --set "$NAME" \
+ label="$COUNT / $USAGE%" \
+ icon.color=$COLOR
diff --git a/mac/.config/sketchybar/plugins/messages.sh b/mac/.config/sketchybar/plugins/messages.sh
new file mode 100644
index 0000000..1ca1ec1
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/messages.sh
@@ -0,0 +1,7 @@
+TEXT=$(sqlite3 ~/Library/Messages/chat.db "SELECT text FROM message WHERE is_read=0 AND is_from_me=0 AND text!='' AND date_read=0" | wc -l | awk '{$1=$1};1')
+
+if [ $TEXT = 0 ]; then
+ sketchybar -m --set $NAME drawing=off
+else
+ sketchybar -m --set $NAME drawing=on label="$TEXT"
+fi \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/mic.sh b/mac/.config/sketchybar/plugins/mic.sh
new file mode 100644
index 0000000..35cde89
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/mic.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+MIC_VOLUME=$(osascript -e 'input volume of (get volume settings)')
+
+if [[ $MIC_VOLUME -eq 0 ]]; then
+ sketchybar -m --set mic icon=
+elif [[ $MIC_VOLUME -gt 0 ]]; then
+ sketchybar -m --set mic icon=
+fi
diff --git a/mac/.config/sketchybar/plugins/mic_click.sh b/mac/.config/sketchybar/plugins/mic_click.sh
new file mode 100644
index 0000000..90a6ac6
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/mic_click.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+MIC_VOLUME=$(osascript -e 'input volume of (get volume settings)')
+
+if [[ $MIC_VOLUME -eq 0 ]]; then
+ osascript -e 'set volume input volume 25'
+ sketchybar -m --set mic icon=
+elif [[ $MIC_VOLUME -gt 0 ]]; then
+ osascript -e 'set volume input volume 0'
+ sketchybar -m --set mic icon=
+fi
diff --git a/mac/.config/sketchybar/plugins/music.sh b/mac/.config/sketchybar/plugins/music.sh
new file mode 100644
index 0000000..ce23701
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/music.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+music_item_defaults=(
+ align=center
+ width=240
+ label.max_chars=33
+)
+
+music_cover=(
+ background.image=media.artwork
+ background.image.scale=5
+ background.image.corner_radius=4
+ background.image.padding_left=$PADDINGS
+ background.image.padding_right=$PADDINGS
+ background.label.align=center
+ y_offset=-$PADDINGS
+ align=center
+)
+
+music_artist=(
+ "${music_item_defaults[@]}"
+)
+
+music_title=(
+ "${music_item_defaults[@]}"
+ label.font.style="Bold"
+)
+
+music_album=(
+ "${music_item_defaults[@]}"
+)
+
+render_bar_item() {
+ sketchybar --set $NAME label="$CURRENT_ARTIST: $CURRENT_SONG"
+}
+
+render_popup() {
+ sketchybar --set $NAME.cover "${music_cover[@]}" \
+ --set $NAME.artist "${music_artist[@]}" \
+ --set $NAME.title "${music_title[@]}" \
+ --set $NAME.album "${music_album[@]}"
+}
+
+update() {
+ CURRENT_ARTIST="$(echo "$INFO" | jq -r '.artist')"
+ CURRENT_SONG="$(echo "$INFO" | jq -r '.title')"
+ CURRENT_ALBUM="$(echo "$INFO" | jq -r '.album')"
+ PLAYER_STATE="$(echo "$INFO" | jq -r '.state')"
+
+ if [ "$PLAYER_STATE" = "playing" ]; then
+ sketchybar --set $NAME drawing=on \
+ icon=􀊆 \
+ --set $NAME.artist label="$CURRENT_ARTIST" \
+ --set $NAME.title label="$CURRENT_SONG" \
+ --set $NAME.album label="$CURRENT_ALBUM"
+ render_bar_item
+ render_popup
+
+ else
+ sketchybar --set $NAME icon=􀊄
+ popup off
+ sketchybar --set $NAME drawing=off
+ fi
+
+}
+
+popup() {
+ sketchybar --set "$NAME" popup.drawing="$1"
+}
+
+playpause() {
+ osascript -e 'tell application "Music" to playpause'
+}
+
+
+case "$SENDER" in
+"routine" | "forced" | "media_change")
+ update
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+"mouse.clicked")
+ playpause
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/music/Cover-Default.png b/mac/.config/sketchybar/plugins/music/Cover-Default.png
new file mode 100644
index 0000000..a65c63f
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/music/Cover-Default.png
Binary files differ
diff --git a/mac/.config/sketchybar/plugins/music/Get-Artwork.applescript b/mac/.config/sketchybar/plugins/music/Get-Artwork.applescript
new file mode 100644
index 0000000..ca3ee31
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/music/Get-Artwork.applescript
@@ -0,0 +1,65 @@
+--get current path
+tell application "Finder" to set current_path to container of (path to me) as alias
+
+--get artwork
+tell application "Music"
+ try
+ if player state is not stopped then
+ set alb to (get album of current track)
+ tell artwork 1 of current track
+ if format is JPEG picture then
+ set imgFormat to ".jpg"
+ else
+ set imgFormat to ".png"
+ end if
+ end tell
+ set rawData to (get raw data of artwork 1 of current track)
+ else
+ return
+ end if
+ on error
+ return POSIX path of ((current_path as text) & "Cover-Default.png")
+ return
+ end try
+end tell
+
+--create path to save image as jpg or png
+set newPath to ((current_path as text) & "tmp" & imgFormat) as text
+
+try
+ --create file
+ tell me to set fileRef to (open for access newPath with write permission)
+ --overwrite existing file
+ write rawData to fileRef starting at 0
+ tell me to close access fileRef
+
+ delay 1
+
+ --resize image
+ set the target_length to 500
+ try
+ tell application "Image Events"
+ -- start the Image Events application
+ launch
+ -- open the image file
+ set this_image to open newPath
+ -- perform action
+ scale this_image to size target_length
+ -- save the changes
+ save this_image with icon
+ -- purge the open image data
+ close this_image
+ end tell
+ on error
+ return POSIX path of ((current_path as text) & "Cover-Default.png")
+ end try
+
+ return POSIX path of newPath
+on error m number n
+ log n
+ log m
+ try
+ tell me to close access fileRef
+ end try
+ return ((current_path as text) & "Cover-Default.png") as text
+end try \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/network.sh b/mac/.config/sketchybar/plugins/network.sh
new file mode 100644
index 0000000..99fda23
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/network.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/colors.sh" # Loads all defined colors
+source "$CONFIG_DIR/icons.sh" # Loads all defined icons
+
+UPDOWN=$(ifstat -i "en0" -b 0.1 1 | tail -n1)
+DOWN=$(echo "$UPDOWN" | awk "{ print \$1 }" | cut -f1 -d ".")
+UP=$(echo "$UPDOWN" | awk "{ print \$2 }" | cut -f1 -d ".")
+
+DOWN_FORMAT=""
+if [ "$DOWN" -gt "999" ]; then
+ DOWN_FORMAT=$(echo "$DOWN" | awk '{ printf "%03.0f Mbps", $1 / 1000}')
+else
+ DOWN_FORMAT=$(echo "$DOWN" | awk '{ printf "%03.0f kbps", $1}')
+fi
+
+UP_FORMAT=""
+if [ "$UP" -gt "999" ]; then
+ UP_FORMAT=$(echo "$UP" | awk '{ printf "%03.0f Mbps", $1 / 1000}')
+else
+ UP_FORMAT=$(echo "$UP" | awk '{ printf "%03.0f kbps", $1}')
+fi
+
+sketchybar -m --set network.down label="$DOWN_FORMAT" icon.highlight=$(if [ "$DOWN" -gt "0" ]; then echo "on"; else echo "off"; fi) \
+ --set network.up label="$UP_FORMAT" icon.highlight=$(if [ "$UP" -gt "0" ]; then echo "on"; else echo "off"; fi)
diff --git a/mac/.config/sketchybar/plugins/nextevent.applescript b/mac/.config/sketchybar/plugins/nextevent.applescript
new file mode 100644
index 0000000..6ffc56a
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/nextevent.applescript
@@ -0,0 +1,19 @@
+set input to paragraphs of (do shell script "/opt/homebrew/bin/icalBuddy -ec 'Found in Natural Language,CCSF' -npn -nc -iep 'datetime,title' -po 'datetime,title' -eed -ea -n -li 4 -ps '|: |' -b '' eventsToday")
+
+set currentTime to date (do shell script "date '+%I:%M %p'")
+
+set theEvent to ""
+
+if input is not "" then
+ repeat with anEvent in input
+ set text item delimiters to "^"
+ set eventTime to date (text item 1 of anEvent)
+ set text item delimiters to ""
+ if eventTime > currentTime then
+ set theEvent to anEvent as string
+ exit repeat
+ end if
+ end repeat
+end if
+
+return theEvent \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/nextevent.sh b/mac/.config/sketchybar/plugins/nextevent.sh
new file mode 100644
index 0000000..732629a
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/nextevent.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+render_item() {
+ sketchybar --set $NAME label="$(date "+%I:%M %p")"
+}
+
+render_popup() {
+
+ if which "icalBuddy" &>/dev/null; then
+ input=$(/opt/homebrew/bin/icalBuddy -ec 'Found in Natural Language,CCSF' -npn -nc -iep 'datetime,title' -po 'datetime,title' -eed -ea -n -li 4 -ps '|: |' -b '' eventsToday)
+ currentTime=$(date '+%I:%M %p')
+
+ # echo "Debug: $NAME #11 $input"
+
+ if [ -n "$input" ]; then
+ IFS='^' read -ra events <<< "$input"
+ for anEvent in "${events[@]}"; do
+ IFS='^' read -ra eventItems <<< "$anEvent"
+ eventTime=${eventItems[0]}
+ if [ "$eventTime" '>' "$currentTime" ]; then
+ theEvent="$anEvent"
+ break
+ fi
+ done
+ else
+ theEvent="No events today"
+ fi
+ else
+ theEvent="Please install icalBuddy → brew install ical-buddy."
+ fi
+
+
+ sketchybar --set clock.details label="$theEvent" click_script="sketchybar --set $NAME popup.drawing=off" >/dev/null
+}
+
+update() {
+ render_item
+}
+
+popup() {
+ render_popup
+ sketchybar --set "$NAME" popup.drawing="$1"
+}
+
+case "$SENDER" in
+"routine" | "forced")
+ update
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+esac \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/space.py b/mac/.config/sketchybar/plugins/space.py
new file mode 100644
index 0000000..7ed3e55
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/space.py
@@ -0,0 +1,199 @@
+#!/usr/bin/python3
+
+import os, json, re
+
+ICON_MAP = [
+ {"regex": r"1Password", "icon": ":one_password:"},
+ {"regex": r"Affinity Designer", "icon": ":affinity_designer:"},
+ {"regex": r"Affinity Photo", "icon": ":affinity_photo:"},
+ {"regex": r"Affinity Publisher", "icon": ":affinity_publisher:"},
+ {"regex": r"Airmail", "icon": ":airmail:"},
+ {"regex": r"Alacritty|Hyper|iTerm2|kitty|Terminal|WezTerm", "icon": ":terminal:"},
+ {"regex": r"Alfred", "icon": ":alfred:"},
+ {"regex": r"Android Messages", "icon": ":android_messages:"},
+ {"regex": r"Android Studio", "icon": ":android_studio:"},
+ {"regex": r"App Store", "icon": ":app_store:"},
+ {"regex": r"Atom", "icon": ":atom:"},
+ {"regex": r"Audacity", "icon": ":audacity:"},
+ {"regex": r"Bear", "icon": ":bear:"},
+ {"regex": r"Bitwarden", "icon": ":bit_warden:"},
+ {"regex": r"Blender", "icon": ":blender:"},
+ {"regex": r"Brave Browser", "icon": ":brave_browser:"},
+ {"regex": r"Calendar|Fantastical|Cron|Morgen", "icon": ":calendar:"},
+ {"regex": r"Calibre", "icon": ":book:"},
+ {
+ "regex": r"Canary Mail|HEY|Mail|Mailspring|MailMate|邮件|Outlook|Gmail|Mimestream",
+ "icon": ":mail:",
+ },
+ {"regex": r"Caprine", "icon": ":caprine:"},
+ {
+ "regex": r"Chromium|Google Chrome|Google Chrome Canary",
+ "icon": ":google_chrome:",
+ },
+ {"regex": r"CleanMyMac X", "icon": ":desktop:"},
+ {"regex": r"ClickUp", "icon": ":click_up:"},
+ {"regex": r"Code|Code - Insiders", "icon": ":code:"},
+ {"regex": r"Color Picker", "icon": ":color_picker:"},
+ {"regex": r"DataGrip", "icon": ":datagrip:"},
+ {"regex": r"Default", "icon": ":default:"},
+ {"regex": r"DEVONthink 3", "icon": ":devonthink3:"},
+ {"regex": r"Discord|Discord Canary|Discord PTB", "icon": ":discord:"},
+ {"regex": r"Drafts", "icon": ":drafts:"},
+ {"regex": r"Dropbox", "icon": ":dropbox:"},
+ {"regex": r"Element", "icon": ":element:"},
+ {"regex": r"Emacs", "icon": ":emacs:"},
+ {"regex": r"Evernote Legacy", "icon": ":evernote_legacy:"},
+ {"regex": r"FaceTime", "icon": ":face_time:"},
+ {"regex": r"Figma", "icon": ":figma:"},
+ {"regex": r"Final Cut Pro", "icon": ":final_cut_pro:"},
+ {"regex": r"Finder|访达", "icon": ":finder:"},
+ {
+ "regex": r"Firefox Developer Edition|Firefox Nightly",
+ "icon": ":firefox_developer_edition:",
+ },
+ {"regex": r"Firefox", "icon": ":firefox:"},
+ {"regex": r"Folx", "icon": ":folx:"},
+ {"regex": r"GitHub Desktop", "icon": ":git_hub:"},
+ {"regex": r"Grammarly Editor", "icon": ":grammarly:"},
+ {"regex": r"GrandTotal|Receipts", "icon": ":dollar:"},
+ {"regex": r"IINA", "icon": ":playing:"},
+ {"regex": r"Insomnia", "icon": ":insomnia:"},
+ {"regex": r"IntelliJ IDEA", "icon": ":idea:"},
+ {"regex": r"Iris", "icon": ":iris:"},
+ {"regex": r"Joplin", "icon": ":joplin:"},
+ {"regex": r"Kakoune", "icon": ":kakoune:"},
+ {"regex": r"KeePassXC", "icon": ":kee_pass_x_c:"},
+ {"regex": r"Keyboard Maestro", "icon": ":keyboard_maestro:"},
+ {"regex": r"Keynote", "icon": ":keynote:"},
+ {"regex": r"League of Legends", "icon": ":league_of_legends:"},
+ {"regex": r"LibreWolf", "icon": ":libre_wolf:"},
+ {"regex": r"Linear", "icon": ":linear:"},
+ {"regex": r"Live", "icon": ":ableton:"},
+ {"regex": r"MAMP|MAMP PRO", "icon": ":mamp:"},
+ {"regex": r"Matlab", "icon": ":matlab:"},
+ {"regex": r"Mattermost", "icon": ":mattermost:"},
+ {"regex": r"Messages|Nachrichten", "icon": ":messages:"},
+ {"regex": r"Microsoft Edge", "icon": ":microsoft_edge:"},
+ {"regex": r"Microsoft Excel", "icon": ":microsoft_excel:"},
+ {"regex": r"Microsoft PowerPoint", "icon": ":microsoft_power_point:"},
+ {"regex": r"Microsoft Teams", "icon": ":microsoft_teams:"},
+ {"regex": r"Microsoft To Do|Things", "icon": ":things:"},
+ {"regex": r"Microsoft Word", "icon": ":microsoft_word:"},
+ {"regex": r"Min", "icon": ":min_browser:"},
+ {"regex": r"MoneyMoney", "icon": ":bank:"},
+ {"regex": r"mpv", "icon": ":mpv:"},
+ {"regex": r"Music", "icon": ":music:"},
+ {"regex": r"Neovide|MacVim|Vim|VimR", "icon": ":vim:"},
+ {"regex": r"Notability", "icon": ":notability:"},
+ {"regex": r"Notes", "icon": ":notes:"},
+ {"regex": r"Notion", "icon": ":notion:"},
+ {"regex": r"Nova", "icon": ":nova:"},
+ {"regex": r"Numbers", "icon": ":numbers:"},
+ {"regex": r"OBS", "icon": ":obsstudio:"},
+ {"regex": r"Obsidian", "icon": ":obsidian:"},
+ {"regex": r"OmniFocus", "icon": ":omni_focus:"},
+ {"regex": r"Pages", "icon": ":pages:"},
+ {"regex": r"Parallels Desktop", "icon": ":parallels:"},
+ {"regex": r"Pi-hole Remote", "icon": ":pihole:"},
+ {"regex": r"Pine", "icon": ":pine:"},
+ {"regex": r"Podcasts", "icon": ":podcasts:"},
+ {"regex": r"PomoDone App", "icon": ":pomodone:"},
+ {"regex": r"Preview|Skim|zathura|PDFgear", "icon": ":pdf:"},
+ {"regex": r"qutebrowser", "icon": ":qute_browser:"},
+ {"regex": r"Reeder", "icon": ":reeder5:"},
+ {"regex": r"Reminders", "icon": ":reminders:"},
+ {"regex": r"Safari|Safari Technology Preview|Orion", "icon": ":safari:"},
+ {"regex": r"Sequel Ace", "icon": ":sequel_ace:"},
+ {"regex": r"Sequel Pro", "icon": ":sequel_pro:"},
+ {"regex": r"Setapp", "icon": ":setapp:"},
+ {"regex": r"Signal", "icon": ":signal:"},
+ {"regex": r"Sketch", "icon": ":sketch:"},
+ {"regex": r"Skype", "icon": ":skype:"},
+ {"regex": r"Slack", "icon": ":slack:"},
+ {"regex": r"Spark", "icon": ":spark:"},
+ {"regex": r"Spotify", "icon": ":spotify:"},
+ {"regex": r"Spotlight", "icon": ":spotlight:"},
+ {"regex": r"Sublime Text", "icon": ":sublime_text:"},
+ {"regex": r"System Preferences|System Settings", "icon": ":gear:"},
+ {"regex": r"TeamSpeak 3", "icon": ":team_speak:"},
+ {"regex": r"Telegram", "icon": ":telegram:"},
+ {"regex": r"Thunderbird", "icon": ":thunderbird:"},
+ {"regex": r"TickTick", "icon": ":tick_tick:"},
+ {"regex": r"TIDAL", "icon": ":tidal:"},
+ {"regex": r"Todoist", "icon": ":todoist:"},
+ {"regex": r"Tor Browser", "icon": ":tor_browser:"},
+ {"regex": r"Tower", "icon": ":tower:"},
+ {"regex": r"Transmit", "icon": ":transmit:"},
+ {"regex": r"Trello", "icon": ":trello:"},
+ {"regex": r"Tweetbot|Twitter", "icon": ":twitter:"},
+ {"regex": r"Typora", "icon": ":text:"},
+ {"regex": r"Vivaldi", "icon": ":vivaldi:"},
+ {"regex": r"VLC", "icon": ":vlc:"},
+ {"regex": r"VMware Fusion", "icon": ":vmware_fusion:"},
+ {"regex": r"VSCodium", "icon": ":vscodium:"},
+ {"regex": r"WebStorm", "icon": ":web_storm:"},
+ {"regex": r"WhatsApp", "icon": ":whats_app:"},
+ {"regex": r"Xcode", "icon": ":xcode:"},
+ {"regex": r"Zeplin", "icon": ":zeplin:"},
+ {"regex": r"zoom.us", "icon": ":zoom:"},
+ {"regex": r"Zotero", "icon": ":zotero:"},
+ {"regex": r"Zulip", "icon": ":zulip:"},
+ {"regex": r"微信", "icon": ":wechat:"},
+ {"regex": r"网易云音乐", "icon": ":netease_music:"},
+]
+
+
+def to_sup(s):
+ sups = {
+ "0": "\u2070",
+ "1": "\xb9",
+ "2": "\xb2",
+ "3": "\xb3",
+ "4": "\u2074",
+ "5": "\u2075",
+ "6": "\u2076",
+ "7": "\u2077",
+ "8": "\u2078",
+ "9": "\u2079",
+ }
+
+ return "".join(sups.get(char, char) for char in str(s))
+
+
+def to_icon(app):
+ for x in ICON_MAP:
+ if re.search(x["regex"], app):
+ return x["icon"]
+ return ":default:"
+
+
+def to_formatted_icon(app, c):
+ cnt = f" {to_sup(c)}" if c > 1 else ""
+ return f"{to_icon(app)}{cnt}"
+
+
+def to_formatted_icons(apps):
+ return " ".join([to_formatted_icon(app, cnt) for app, cnt in apps.items()])
+
+
+spaces = {}
+apps = json.loads(os.popen("yabai -m query --windows").read())
+for app in apps:
+ spaces[app["space"]] = spaces.get(app["space"], {})
+ spaces[app["space"]][app["app"]] = spaces[app["space"]].get(app["app"], 0) + 1
+
+args = " ".join(
+ [
+ f'--set space.{space} label="{to_formatted_icons(apps)}" label.drawing=on'
+ for space, apps in spaces.items()
+ ]
+)
+default_args = "--set spaces_bracket drawing=off --set '/space\..*/' background.drawing=on --animate sin 10"
+
+args2 = ""
+spaces = json.loads(os.popen("yabai -m query --spaces").read())
+for space in spaces:
+ if not space["windows"]:
+ args2 = f'{args2} --set space.{space["index"]} label="" label.drawing=off'
+
+os.system(f"sketchybar -m {default_args} {args} {args2}")
diff --git a/mac/.config/sketchybar/plugins/space.sh b/mac/.config/sketchybar/plugins/space.sh
new file mode 100644
index 0000000..70f3ac4
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/space.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/globalstyles.sh"
+
+if [ "$SELECTED" = "true" ]; then
+ COLOR=$HIGHLIGHT
+ OFFSET=-12 # under line
+ WIDTH="dynamic"
+else
+ COLOR=$TRANSPARENT
+fi
+
+sketchybar --animate tanh 10 \
+ --set $NAME icon.highlight=$SELECTED \
+ label.highlight=$SELECTED \
+ background.color=$COLOR \
+ background.y_offset=$OFFSET
+
+update() {
+ source "$CONFIG_DIR/colors.sh"
+ COLOR=$BACKGROUND_2
+ WIDTH="dynamic"
+
+ sketchybar --set $NAME icon.highlight=$SELECTED \
+ label.highlight=$SELECTED \
+ background.border_color=$COLOR \
+ sketchybar --animate tanh 8 \
+ label.width=$WIDTH \
+}
+
+set_space_label() {
+ sketchybar --set $NAME icon="$@"
+}
+
+mouse_clicked() {
+ if [ "$BUTTON" = "right" ]; then
+ yabai -m space --destroy $SID
+ sketchybar --trigger space_change --trigger windows_on_spaces
+ else
+ if [ "$MODIFIER" = "shift" ]; then
+ SPACE_LABEL="$(osascript -e "return (text returned of (display dialog \"Give a name to space $NAME:\" default answer \"\" with icon note buttons {\"Cancel\", \"Continue\"} default button \"Continue\"))")"
+ if [ $? -eq 0 ]; then
+ if [ "$SPACE_LABEL" = "" ]; then
+ set_space_label "${NAME:6}"
+ else
+ set_space_label "${NAME:6} ($SPACE_LABEL)"
+ fi
+ fi
+ else
+ yabai -m space --focus $SID 2>/dev/null
+ fi
+ fi
+}
+
+case "$SENDER" in
+"mouse.clicked")
+ mouse_clicked
+ ;;
+*)
+ update
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/space_windows.sh b/mac/.config/sketchybar/plugins/space_windows.sh
new file mode 100644
index 0000000..f175891
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/space_windows.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+if [ "$SENDER" = "space_windows_change" ]; then
+ args=(--animate sin 10)
+
+ space="$(echo "$INFO" | jq -r '.space')"
+ apps="$(echo "$INFO" | jq -r '.apps | keys[]')"
+
+ icon_strip=" "
+ if [ "${apps}" != "" ]; then
+ while read -r app; do
+ icon_strip+=" $($CONFIG_DIR/plugins/icon_map.sh "$app")"
+ done <<<"${apps}"
+ else
+ icon_strip=""
+ fi
+ args+=(--set space.$space label="$icon_strip")
+
+ sketchybar -m "${args[@]}"
+fi
diff --git a/mac/.config/sketchybar/plugins/spotify.sh b/mac/.config/sketchybar/plugins/spotify.sh
new file mode 100644
index 0000000..521ac60
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/spotify.sh
@@ -0,0 +1,147 @@
+#!/bin/bash
+
+next ()
+{
+ osascript -e 'tell application "Spotify" to play next track'
+}
+
+back ()
+{
+ osascript -e 'tell application "Spotify" to play previous track'
+}
+
+play ()
+{
+ osascript -e 'tell application "Spotify" to playpause'
+}
+
+repeat ()
+{
+ REPEAT=$(osascript -e 'tell application "Spotify" to get repeating')
+ if [ "$REPEAT" = "false" ]; then
+ sketchybar -m --set spotify.repeat icon.highlight=on
+ osascript -e 'tell application "Spotify" to set repeating to true'
+ else
+ sketchybar -m --set spotify.repeat icon.highlight=off
+ osascript -e 'tell application "Spotify" to set repeating to false'
+ fi
+}
+
+shuffle ()
+{
+ SHUFFLE=$(osascript -e 'tell application "Spotify" to get shuffling')
+ if [ "$SHUFFLE" = "false" ]; then
+ sketchybar -m --set spotify.shuffle icon.highlight=on
+ osascript -e 'tell application "Spotify" to set shuffling to true'
+ else
+ sketchybar -m --set spotify.shuffle icon.highlight=off
+ osascript -e 'tell application "Spotify" to set shuffling to false'
+ fi
+}
+
+update ()
+{
+ PLAYING=1
+ if [ "$(echo "$INFO" | jq -r '.["Player State"]')" = "Playing" ]; then
+ PLAYING=0
+ TRACK="$(echo "$INFO" | jq -r .Name)"
+ ARTIST="$(echo "$INFO" | jq -r .Artist)"
+ ALBUM="$(echo "$INFO" | jq -r .Album)"
+ SHUFFLE=$(osascript -e 'tell application "Spotify" to get shuffling')
+ REPEAT=$(osascript -e 'tell application "Spotify" to get repeating')
+ COVER=$(osascript -e 'tell application "Spotify" to get artwork url of current track')
+ fi
+
+ args=()
+ if [ $PLAYING -eq 0 ]; then
+ curl -s --max-time 20 "$COVER" -o /tmp/cover.jpg
+ if [ "$ARTIST" == "" ]; then
+ args+=(--set spotify.title label="$TRACK"
+ --set spotify.album label="Podcast"
+ --set spotify.artist label="$ALBUM" )
+ else
+ args+=(--set spotify.title label="$TRACK"
+ --set spotify.album label="$ALBUM"
+ --set spotify.artist label="$ARTIST")
+ fi
+ args+=(--set spotify.play icon=􀊆
+ --set spotify.shuffle icon.highlight=$SHUFFLE
+ --set spotify.repeat icon.highlight=$REPEAT
+ --set spotify.cover background.image="/tmp/cover.jpg"
+ background.color=0x00000000
+ --set spotify.anchor drawing=on )
+ else
+ args+=(--set spotify.anchor drawing=off popup.drawing=off
+ --set spotify.play icon=􀊄 )
+ fi
+ sketchybar -m "${args[@]}"
+}
+
+scrubbing() {
+ DURATION_MS=$(osascript -e 'tell application "Spotify" to get duration of current track')
+ DURATION=$((DURATION_MS/1000))
+
+ TARGET=$((DURATION*PERCENTAGE/100))
+ osascript -e "tell application \"Spotify\" to set player position to $TARGET"
+ sketchybar --set spotify.state slider.percentage=$PERCENTAGE
+}
+
+scroll() {
+ DURATION_MS=$(osascript -e 'tell application "Spotify" to get duration of current track')
+ DURATION=$((DURATION_MS/1000))
+
+ FLOAT="$(osascript -e 'tell application "Spotify" to get player position')"
+ TIME=${FLOAT%.*}
+
+ sketchybar --animate linear 10 \
+ --set spotify.state slider.percentage="$((TIME*100/DURATION))" \
+ icon="$(date -r $TIME +'%M:%S')" \
+ label="$(date -r $DURATION +'%M:%S')"
+}
+
+mouse_clicked () {
+ case "$NAME" in
+ "spotify.next") next
+ ;;
+ "spotify.back") back
+ ;;
+ "spotify.play") play
+ ;;
+ "spotify.shuffle") shuffle
+ ;;
+ "spotify.repeat") repeat
+ ;;
+ "spotify.state") scrubbing
+ ;;
+ *) exit
+ ;;
+ esac
+}
+
+popup () {
+ sketchybar --set spotify.anchor popup.drawing=$1
+}
+
+routine() {
+ case "$NAME" in
+ "spotify.state") scroll
+ ;;
+ *) update
+ ;;
+ esac
+}
+
+case "$SENDER" in
+ "mouse.clicked") mouse_clicked
+ ;;
+ "mouse.entered") popup on
+ ;;
+ "mouse.exited"|"mouse.exited.global") popup off
+ ;;
+ "routine") routine
+ ;;
+ "forced") exit 0
+ ;;
+ *) update
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/svim.sh b/mac/.config/sketchybar/plugins/svim.sh
new file mode 100644
index 0000000..cb792b8
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/svim.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+source "$CONFIG_DIR/icons.sh"
+source "$CONFIG_DIR/colors.sh"
+
+if [ "$SENDER" = "svim_update" ]; then
+ DRAWING=on
+ DRAW_CMD=off
+ COLOR=$WHITE
+ case "$MODE" in
+ "I") ICON="$MODE_INSERT" DRAWING=off
+ ;;
+ "N") ICON="$MODE_NORMAL"
+ ;;
+ "V") ICON="$MODE_VISUAL" COLOR=$YELLOW
+ ;;
+ "C") ICON="$MODE_CMD" DRAW_CMD=on COLOR=$RED
+ ;;
+ "_") ICON="$MODE_PENDING"
+ ;;
+ *) DRAWING=off
+ ;;
+ esac
+
+ sketchybar --set $NAME drawing="$DRAWING" \
+ label.drawing="$DRAW_CMD" \
+ icon="$ICON" \
+ icon.color="$COLOR" \
+ label="$CMDLINE"
+fi
diff --git a/mac/.config/sketchybar/plugins/thunderbird.sh b/mac/.config/sketchybar/plugins/thunderbird.sh
new file mode 100644
index 0000000..53f8e31
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/thunderbird.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+TEXT=$(lsappinfo info -only StatusLabel "Thunderbird" | grep -o '"label"="[0-9]*"' | awk -F'"' '{print $4}')
+
+if [[ $TEXT -gt 0 ]]; then
+ sketchybar -m --set $NAME drawing=on label="$TEXT"
+else
+ sketchybar -m --set $NAME drawing=off
+fi
diff --git a/mac/.config/sketchybar/plugins/toggle_stats.sh b/mac/.config/sketchybar/plugins/toggle_stats.sh
new file mode 100644
index 0000000..b364c18
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/toggle_stats.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+stats=(
+ cpu.top
+ cpu.percent
+ cpu.sys
+ cpu.user
+ memory
+ disk
+ network.up
+ network.down
+)
+
+hide_stats() {
+ args=()
+ for item in "${stats[@]}"; do
+ args+=(--set "$item" drawing=off)
+ done
+
+ sketchybar "${args[@]}" \
+ --set separator_right \
+ icon=󰅂 \
+ icon.font.size=25 \
+ padding_right=10
+}
+
+show_stats() {
+ args=()
+ for item in "${stats[@]}"; do
+ args+=(--set "$item" drawing=on)
+ done
+
+ sketchybar "${args[@]}" \
+ --set separator_right \
+ icon=󰅁 \
+ icon.font.size=25 \
+ padding_right=10
+}
+
+toggle_stats() {
+ state=$(sketchybar --query separator_right | jq -r .icon.value)
+
+ case $state in
+ "󰅂")
+ show_stats
+ ;;
+ "󰅁")
+ hide_stats
+ ;;
+ esac
+}
+
+case "$SENDER" in
+"hide_stats")
+ hide_stats
+ ;;
+"show_stats")
+ show_stats
+ ;;
+"toggle_stats")
+ toggle_stats
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/volume.sh b/mac/.config/sketchybar/plugins/volume.sh
new file mode 100644
index 0000000..a03a790
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/volume.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+WIDTH=100
+
+volume_change() {
+ source "$CONFIG_DIR/icons.sh"
+ source "$CONFIG_DIR/colors.sh"
+
+ case $INFO in
+ [6-9][0-9]|100) ICON=$VOLUME_100
+ ;;
+ [3-5][0-9]) ICON=$VOLUME_66
+ ;;
+ [1-2][0-9]) ICON=$VOLUME_33
+ ;;
+ [1-9]) ICON=$VOLUME_10
+ ;;
+ 0) ICON=$VOLUME_0
+ ;;
+ *) ICON=$VOLUME_100
+ esac
+
+ sketchybar --set volume_icon icon=$ICON
+ sketchybar --set $NAME slider.percentage=$INFO --animate tanh 30 --set $NAME slider.width=$WIDTH
+ sleep 2
+
+ # Check wether the volume was changed another time while sleeping
+ FINAL_PERCENTAGE=$(sketchybar --query $NAME | jq -r ".slider.percentage")
+ if [ "$FINAL_PERCENTAGE" -eq "$INFO" ]; then
+ sketchybar --animate tanh 30 --set $NAME slider.width=0
+ fi
+}
+
+mouse_clicked() {
+ osascript -e "set volume output volume $PERCENTAGE"
+}
+
+case "$SENDER" in
+ "volume_change") volume_change
+ ;;
+ "mouse.clicked") mouse_clicked
+ ;;
+esac \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/volume_click.sh b/mac/.config/sketchybar/plugins/volume_click.sh
new file mode 100644
index 0000000..e05c0d8
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/volume_click.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+WIDTH=100
+
+detail_on() {
+ sketchybar --animate tanh 30 --set volume slider.width=$WIDTH
+}
+
+detail_off() {
+ sketchybar --animate tanh 30 --set volume slider.width=0
+}
+
+toggle_detail() {
+ INITIAL_WIDTH=$(sketchybar --query volume | jq -r ".slider.width")
+ if [ "$INITIAL_WIDTH" -eq "0" ]; then
+ detail_on
+ else
+ detail_off
+ fi
+}
+
+toggle_devices() {
+ which SwitchAudioSource >/dev/null || exit 0
+ source "$CONFIG_DIR/colors.sh"
+ source "$CONFIG_DIR/globalstyles.sh"
+
+ args=(--remove '/volume.device\.*/' --set "$NAME" popup.drawing=toggle "${menu_defaults[@]}")
+ COUNTER=0
+ CURRENT="$(SwitchAudioSource -t output -c)"
+ while IFS= read -r device; do
+ COLOR=$WHITE
+ ICON=􀆅
+ ICON_COLOR=$TRANSPARENT
+ if [ "${device}" = "$CURRENT" ]; then
+ COLOR=$HIGHLIGHT
+ ICON=􀆅
+ ICON_COLOR=$COLOR
+ fi
+
+ args+=(--add item volume.device.$COUNTER popup."$NAME" \
+ --set volume.device.$COUNTER label="${device}" \
+ label.color="$COLOR" \
+ icon=$ICON \
+ icon.color=$ICON_COLOR \
+ "${menu_item_defaults[@]}" \
+ click_script="SwitchAudioSource -s \"${device}\" && sketchybar --set /volume.device\.*/ label.color=$GREY --set \$NAME label.color=$WHITE --set $NAME popup.drawing=off")
+ COUNTER=$((COUNTER+1))
+ done <<< "$(SwitchAudioSource -a -t output)"
+
+ sketchybar -m "${args[@]}" > /dev/null
+}
+
+if [ "$BUTTON" = "left" ] || [ "$MODIFIER" = "shift" ]; then
+ toggle_devices
+else
+ toggle_detail
+fi \ No newline at end of file
diff --git a/mac/.config/sketchybar/plugins/weather.sh b/mac/.config/sketchybar/plugins/weather.sh
new file mode 100644
index 0000000..61644de
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/weather.sh
@@ -0,0 +1,191 @@
+#!/usr/bin/env bash
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+# API_KEY="462eeb49a1b844f191f175554222607" # insert api key here
+API_KEY="2aa1a4f60e2542efae051005240402" # insert api key here
+
+# first comment is description, second is icon number
+WEATHER_ICONS_DAY=(
+ [1000]= # Sunny/113
+ [1003]= # Partly cloudy/116
+ [1006]= # Cloudy/119
+ [1009]= # Overcast/122
+ [1030]= # Mist/143
+ [1063]= # Patchy rain possible/176
+ [1066]= # Patchy snow possible/179
+ [1069]= # Patchy sleet possible/182
+ [1072]= # Patchy freezing drizzle possible/185
+ [1087]= # Thundery outbreaks possible/200
+ [1114]= # Blowing snow/227
+ [1117]= # Blizzard/230
+ [1135]= # Fog/248
+ [1147]= # Freezing fog/260
+ [1150]= # Patchy light drizzle/263
+ [1153]= # Light drizzle/266
+ [1168]= # Freezing drizzle/281
+ [1171]= # Heavy freezing drizzle/284
+ [1180]= # Patchy light rain/293
+ [1183]= # Light rain/296
+ [1186]= # Moderate rain at times/299
+ [1189]= # Moderate rain/302
+ [1192]= # Heavy rain at times/305
+ [1195]= # Heavy rain/308
+ [1198]= # Light freezing rain/311
+ [1201]= # Moderate or heavy freezing rain/314
+ [1204]= # Light sleet/317
+ [1207]= # Moderate or heavy sleet/320
+ [1210]= # Patchy light snow/323
+ [1213]= # Light snow/326
+ [1216]= # Patchy moderate snow/329
+ [1219]= # Moderate snow/332
+ [1222]= # Patchy heavy snow/335
+ [1225]= # Heavy snow/338
+ [1237]= # Ice pellets/350
+ [1240]= # Light rain shower/353
+ [1243]= # Moderate or heavy rain shower/356
+ [1246]= # Torrential rain shower/359
+ [1249]= # Light sleet showers/362
+ [1252]= # Moderate or heavy sleet showers/365
+ [1255]= # Light snow showers/368
+ [1258]= # Moderate or heavy snow showers/371
+ [1261]= # Light showers of ice pellets/374
+ [1264]= # Moderate or heavy showers of ice pellets/377
+ [1273]= # Patchy light rain with thunder/386
+ [1276]= # Moderate or heavy rain with thunder/389
+ [1279]= # Patchy light snow with thunder/392
+ [1282]= # Moderate or heavy snow with thunder/395
+)
+
+WEATHER_ICONS_NIGHT=(
+ [1000]= # Clear/113
+ [1003]= # Partly cloudy/116
+ [1006]= # Cloudy/119
+ [1009]= # Overcast/122
+ [1030]= # Mist/143
+ [1063]= # Patchy rain possible/176
+ [1066]= # Patchy snow possible/179
+ [1069]= # Patchy sleet possible/182
+ [1072]= # Patchy freezing drizzle possible/185
+ [1087]= # Thundery outbreaks possible/200
+ [1114]= # Blowing snow/227
+ [1117]= # Blizzard/230
+ [1135]= # Fog/248
+ [1147]= # Freezing fog/260
+ [1150]= # Patchy light drizzle/263
+ [1153]= # Light drizzle/266
+ [1168]= # Freezing drizzle/281
+ [1171]= # Heavy freezing drizzle/284
+ [1180]= # Patchy light rain/293
+ [1183]= # Light rain/296
+ [1186]= # Moderate rain at times/299
+ [1189]= # Moderate rain/302
+ [1192]= # Heavy rain at times/305
+ [1195]= # Heavy rain/308
+ [1198]= # Light freezing rain/311
+ [1201]= # Moderate or heavy freezing rain/314
+ [1204]= # Light sleet/317
+ [1207]= # Moderate or heavy sleet/320
+ [1210]= # Patchy light snow/323
+ [1213]= # Light snow/326
+ [1216]= # Patchy moderate snow/329
+ [1219]= # Moderate snow/332
+ [1222]= # Patchy heavy snow/335
+ [1225]= # Heavy snow/338
+ [1237]= # Ice pellets/350
+ [1240]= # Light rain shower/353
+ [1243]= # Moderate or heavy rain shower/356
+ [1246]= # Torrential rain shower/359
+ [1249]= # Light sleet showers/362
+ [1252]= # Moderate or heavy sleet showers/365
+ [1255]= # Light snow showers/368
+ [1258]= # Moderate or heavy snow showers/371
+ [1261]= # Light showers of ice pellets/374
+ [1264]= # Moderate or heavy showers of ice pellets/377
+ [1273]= # Patchy light rain with thunder/386
+ [1276]= # Moderate or heavy rain with thunder/389
+ [1279]= # Patchy light snow with thunder/392
+ [1282]= # Moderate or heavy snow with thunder/395
+)
+
+render_item() {
+ if [ "$CITY" = "" ]; then
+ args+=(--set $NAME icon="􀌏" label.drawing=off icon.padding_right=-3)
+ else
+ args+=(--set $NAME icon="$ICON" icon.font="Hack Nerd Font:Bold:14.0" label="${TEMP}°C" label.drawing=on icon.padding_right=-3)
+ fi
+
+ sketchybar "${args[@]}" >/dev/null
+
+}
+
+render_popup() {
+ if [ "$CITY" = "" ]; then
+ args+=(--set weather.details label="N/A"
+ click_script="sketchybar --set $NAME popup.drawing=off")
+ else
+ args+=(--set weather.details label="$CONDITION_TEXT, Humidity: $HUMIDITY% ($LOCATION)"
+ click_script="sketchybar --set $NAME popup.drawing=off")
+ fi
+
+ sketchybar "${args[@]}" >/dev/null
+
+}
+
+update() {
+ CURRENT_WIFI="$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I)"
+ SSID="$(echo "$CURRENT_WIFI" | grep -o "SSID: .*" | sed 's/^SSID: //')"
+
+ # Attempt to get the city name using the IP info service
+ CITY="$(curl -s -m 5 ipinfo.io/loc)"
+
+ # Check if the CITY variable is empty or contains an error message
+ if [ -z "$CITY" ] || echo "$CITY" | grep -q "error"; then
+ # Default to Birmingham,AL if the curl command failed or returned an error
+ CITY="Birmingham,AL"
+ fi
+
+ # Replace spaces with '%20' for URL encoding
+ CITY=$(echo "$CITY" | sed 's/ /%20/g')
+
+ if [ "$CITY" != "" ]; then
+ DATA=$(curl -s -m 5 "http://api.weatherapi.com/v1/current.json?key=$API_KEY&q=$CITY")
+ CONDITION=$(echo $DATA | jq -r '.current.condition.code')
+ CONDITION_TEXT=$(echo $DATA | jq -r '.current.condition.text')
+ TEMP=$(echo $DATA | jq -r '.current.temp_c | floor')
+ FEELSLIKE=$(echo $DATA | jq -r '.current.feelslike_f')
+ HUMIDITY=$(echo $DATA | jq -r '.current.humidity')
+ IS_DAY=$(echo $DATA | jq -r '.current.is_day')
+ LOCATION=$(echo $DATA | jq -r '.location.name' && echo ', ' && echo $DATA | jq -r '.location.country')
+
+ [ "$IS_DAY" = "1" ] && ICON=${WEATHER_ICONS_DAY[$CONDITION]} || ICON=${WEATHER_ICONS_NIGHT[$CONDITION]}
+ args=()
+ fi
+
+ render_item
+ render_popup
+
+ if [ "$SENDER" = "forced" ]; then
+ sketchybar --set "$NAME"
+ fi
+}
+
+popup() {
+ sketchybar --set "$NAME" popup.drawing="$1"
+}
+
+case "$SENDER" in
+"routine" | "forced" | "wifi_change")
+ update
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+"mouse.clicked")
+ popup toggle
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/wifi.sh b/mac/.config/sketchybar/plugins/wifi.sh
new file mode 100644
index 0000000..3f78223
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/wifi.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+
+POPUP_OFF="sketchybar --set wifi popup.drawing=off"
+POPUP_CLICK_SCRIPT="sketchybar --set wifi popup.drawing=toggle"
+
+source "$CONFIG_DIR/globalstyles.sh" # Loads defined colors
+
+INFO="$(/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F ' SSID: ' '/ SSID: / {print $2}')"
+IS_VPN=$(/usr/local/bin/piactl get connectionstate)
+CURRENT_WIFI="$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I)"
+IP_ADDRESS="$(ipconfig getifaddr en0)"
+SSID="$(echo "$CURRENT_WIFI" | grep -o "SSID: .*" | sed 's/^SSID: //')"
+CURR_TX="$(echo "$CURRENT_WIFI" | grep -o "lastTxRate: .*" | sed 's/^lastTxRate: //')"
+
+# ICON="$([ -n "$INFO" ] && echo "$WIFI_CONNECTED" || echo "$WIFI_DISCONNECTED")"
+
+if [ -n "$INFO" ]; then
+ ICON_COLOR=$GREEN
+ ICON="$WIFI_CONNECTED"
+elif [ -z "$INFO" ]; then
+ ICON="$WIFI_DISCONNECTED"
+elif [[ $IS_VPN != "Disconnected" ]]; then
+ ICON_COLOR=$HIGHLIGHT
+ ICON=􀎡
+elif [[ $SSID = "Ebrietas" ]]; then
+ ICON_COLOR=$WHITE
+ ICON=􀉤
+elif [[ $SSID != "" ]]; then
+ ICON_COLOR=$WHITE
+ ICON=􀐿
+elif [[ $CURRENT_WIFI = "AirPort: Off" ]]; then
+ ICON_COLOR=$RED
+ ICON=􀐾
+else
+ ICON_COLOR=$WHITE_25
+ ICON=􀐾
+fi
+
+render_bar_item() {
+ sketchybar --set $NAME \
+ icon.color=$ICON_COLOR \
+ icon=$ICON \
+ click_script="$POPUP_CLICK_SCRIPT"
+}
+
+render_popup() {
+ if [ "$SSID" != "" ]; then
+ args=(
+ --set wifi click_script="$POPUP_CLICK_SCRIPT"
+ --set wifi.ssid label="$SSID"
+ --set wifi.strength label="$CURR_TX Mbps"
+ --set wifi.ipaddress label="$IP_ADDRESS"
+ click_script="printf $IP_ADDRESS | pbcopy;$POPUP_OFF"
+ )
+ else
+ args=(
+ --set wifi click_script="")
+ fi
+
+ sketchybar "${args[@]}" >/dev/null
+}
+
+update() {
+ render_bar_item
+ render_popup
+ source "$CONFIG_DIR/icons.sh"
+ INFO="$(/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F ' SSID: ' '/ SSID: / {print $2}')"
+ LABEL="$INFO ($(ipconfig getifaddr en0))"
+ ICON="$([ -n "$INFO" ] && echo "$WIFI_CONNECTED" || echo "$WIFI_DISCONNECTED")"
+ sketchybar --set $NAME icon="$ICON" label="$LABEL"
+}
+
+click() {
+ CURRENT_WIDTH="$(sketchybar --query $NAME | jq -r .label.width)"
+
+ WIDTH=0
+ if [ "$CURRENT_WIDTH" -eq "0" ]; then
+ WIDTH=dynamic
+ fi
+
+ sketchybar --animate sin 20 --set $NAME label.width="$WIDTH"
+}
+
+popup() {
+ sketchybar --set "$NAME" popup.drawing="$1"
+}
+
+case "$SENDER" in
+"routine" | "forced")
+ update
+ ;;
+"mouse.entered")
+ popup on
+ ;;
+"mouse.exited" | "mouse.exited.global")
+ popup off
+ ;;
+"wifi_change")
+ update
+ ;;
+"mouse.clicked")
+ click
+ ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/yabai.sh b/mac/.config/sketchybar/plugins/yabai.sh
new file mode 100644
index 0000000..6027de1
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/yabai.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+# window_state() {
+# source "$CONFIG_DIR/globalstyles.sh"
+#
+# COLOR=$LABEL_COLOR
+#
+# WINDOW=$(yabai -m query --windows --window)
+# read -r FLOATING SPLIT PARENT FULLSCREEN STICKY STACK_INDEX <<<$(echo "$WINDOW" | jq -rc '.["is-floating", "split-type", "has-parent-zoom", "has-fullscreen-zoom", "is-sticky", "stack-index"]')
+#
+# if [[ $STACK_INDEX -gt 0 ]]; then
+# LAST_STACK_INDEX=$(yabai -m query --windows --window stack.last | jq '.["stack-index"]')
+# ICON=$YABAI_STACK
+# LABEL="$(printf "%s/%s " "$STACK_INDEX" "$LAST_STACK_INDEX")"
+# COLOR=$YELLOW
+# elif [[ $FLOATING == "true" ]]; then
+# ICON=$YABAI_FLOAT
+# elif [[ $PARENT == "true" ]]; then
+# ICON="􁈔"
+# elif [[ $FULLSCREEN == "true" ]]; then
+# ICON=$YABAI_FULLSCREEN_ZOOM
+# elif [[ $SPLIT == "vertical" ]]; then
+# ICON=$YABAI_SPLIT_VERTICAL
+# elif [[ $SPLIT == "horizontal" ]]; then
+# ICON=$YABAI_SPLIT_HORIZONTAL
+# else
+# ICON=$YABAI_GRID
+# fi
+#
+# args=(--bar border_color=$COLOR --animate sin 10 --set $NAME icon=$ICON icon.color=$COLOR)
+#
+# [ -z "$LABEL" ] && args+=(label.drawing=off) ||
+# args+=(label.drawing=on label="$LABEL" label.color=$COLOR)
+#
+# [ -z "$ICON" ] && args+=(icon.width=0) ||
+# args+=(icon="$ICON")
+#
+# sketchybar -m "${args[@]}"
+# }
+#
+# windows_on_spaces() {
+# /usr/bin/python3 $CONFIG_DIR/plugins/space.py # New spaces python script to consolidate spaces
+# }
+#
+# mouse_clicked() {
+#
+# yabai_mode=$(yabai -m query --spaces --space | jq -r .type)
+#
+# case "$yabai_mode" in
+# bsp)
+# yabai -m config layout stack
+# ;;
+# stack)
+# yabai -m config layout float
+# ;;
+# float)
+# yabai -m config layout bsp
+# ;;
+# esac
+#
+# window_state
+# }
+#
+# case "$SENDER" in
+# "mouse.clicked")
+# mouse_clicked
+# ;;
+# "forced")
+# exit 0
+# ;;
+# "window_focus")
+# window_state
+# ;;
+# "windows_on_spaces" | "space_change")
+# windows_on_spaces
+# ;;
+# esac
+
+window_state() {
+ source "$CONFIG_DIR/colors.sh"
+ source "$CONFIG_DIR/icons.sh"
+
+ COLOR=$ROSEWATER
+
+ WINDOW=$(yabai -m query --windows --window)
+ read -r FLOATING SPLIT PARENT FULLSCREEN STICKY STACK_INDEX <<<$(echo "$WINDOW" | jq -rc '.["is-floating", "split-type", "has-parent-zoom", "has-fullscreen-zoom", "is-sticky", "stack-index"]')
+
+ if [[ $STACK_INDEX -gt 0 ]]; then
+ LAST_STACK_INDEX=$(yabai -m query --windows --window stack.last | jq '.["stack-index"]')
+ ICON=$YABAI_STACK
+ LABEL="$(printf "%s/%s " "$STACK_INDEX" "$LAST_STACK_INDEX")"
+ COLOR=$YELLOW
+ elif [[ $FLOATING == "true" ]]; then
+ ICON=$YABAI_FLOAT
+ elif [[ $PARENT == "true" ]]; then
+ ICON="􁈔"
+ elif [[ $FULLSCREEN == "true" ]]; then
+ ICON=$YABAI_FULLSCREEN_ZOOM
+ elif [[ $SPLIT == "vertical" ]]; then
+ ICON=$YABAI_SPLIT_VERTICAL
+ elif [[ $SPLIT == "horizontal" ]]; then
+ ICON=$YABAI_SPLIT_HORIZONTAL
+ else
+ ICON=$YABAI_GRID
+ fi
+
+ args=(--bar --animate sin 10 --set $NAME icon=$ICON icon.color=$COLOR)
+ # args=(--bar border_color=$COLOR --animate sin 10 --set $NAME icon=$ICON icon.color=$COLOR)
+
+ [ -z "$LABEL" ] && args+=(label.drawing=off) ||
+ args+=(label.drawing=on label="$LABEL" label.color=$COLOR)
+
+ [ -z "$ICON" ] && args+=(icon.width=0) ||
+ args+=(icon="$ICON")
+
+ sketchybar -m "${args[@]}"
+}
+
+windows_on_spaces() {
+ /usr/bin/python3 $CONFIG_DIR/plugins/space.py # New spaces python script to consolidate spaces
+}
+
+mouse_clicked() {
+ yabai -m window --toggle float
+ window_state
+}
+
+case "$SENDER" in
+"mouse.clicked")
+ mouse_clicked
+ ;;
+"forced")
+ exit 0
+ ;;
+"window_focus")
+ window_state
+ ;;
+# "windows_on_spaces" | "space_change")
+# windows_on_spaces
+# ;;
+esac
diff --git a/mac/.config/sketchybar/plugins/zen.sh b/mac/.config/sketchybar/plugins/zen.sh
new file mode 100644
index 0000000..38f2291
--- /dev/null
+++ b/mac/.config/sketchybar/plugins/zen.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+zen_on() {
+ sketchybar --set wifi drawing=off \
+ --set apple.logo drawing=off \
+ --set '/cpu.*/' drawing=off \
+ --set calendar icon.drawing=off \
+ --set separator drawing=off \
+ --set front_app drawing=off \
+ --set volume_icon drawing=off \
+ --set spotify.anchor drawing=off \
+ --set spotify.play updates=off \
+ --set brew drawing=off \
+ --set volume drawing=off \
+ --set github.bell drawing=off
+}
+
+zen_off() {
+ sketchybar --set wifi drawing=on \
+ --set apple.logo drawing=on \
+ --set '/cpu.*/' drawing=on \
+ --set calendar icon.drawing=on \
+ --set separator drawing=on \
+ --set front_app drawing=on \
+ --set volume_icon drawing=on \
+ --set spotify.play updates=on \
+ --set brew drawing=on \
+ --set volume drawing=on \
+ --set github.bell drawing=on
+}
+
+if [ "$1" = "on" ]; then
+ zen_on
+elif [ "$1" = "off" ]; then
+ zen_off
+else
+ if [ "$(sketchybar --query apple.logo | jq -r ".geometry.drawing")" = "on" ]; then
+ zen_on
+ else
+ zen_off
+ fi
+fi
+
diff --git a/mac/.config/sketchybar/sketchybarrc b/mac/.config/sketchybar/sketchybarrc
new file mode 100644
index 0000000..f97aa3a
--- /dev/null
+++ b/mac/.config/sketchybar/sketchybarrc
@@ -0,0 +1,56 @@
+PLUGIN_DIR="$CONFIG_DIR/plugins"
+ITEM_DIR="$CONFIG_DIR/items"
+
+# Load global styles, colors and icons
+source "$CONFIG_DIR/globalstyles.sh"
+
+POPUP_CLICK_SCRIPT="sketchybar --set \$NAME popup.drawing=toggle"
+POPUP_OFF="sketchybar --set \$NAME popup.drawing=off"
+
+# Setting up and starting the helper process
+HELPER=git.felix.helper
+killall helper
+(cd $CONFIG_DIR/helper && make)
+$CONFIG_DIR/helper/helper $HELPER >/dev/null 2>&1 &
+
+# Unload the macOS on screen indicator overlay for volume change
+launchctl unload -F /System/Library/LaunchAgents/com.apple.OSDUIHelper.plist >/dev/null 2>&1 &
+
+sketchybar --bar "${bar[@]}" --default "${item_defaults[@]}"
+
+# Left
+# source "$ITEM_DIR/apple.sh"
+source "$ITEM_DIR/yabai.sh"
+source "$ITEM_DIR/spaces.sh"
+source "$ITEM_DIR/front_app.sh"
+
+# Center
+# source "$ITEM_DIR/spotify.sh"
+# source "$ITEM_DIR/media.sh"
+source "$ITEM_DIR/music.sh"
+
+# Right
+# source "$ITEM_DIR/datetime.sh"
+# source "$ITEM_DIR/weather.sh"
+source "$ITEM_DIR/github.sh"
+source "$ITEM_DIR/brew.sh"
+# source "$ITEM_DIR/package_monitor.sh"
+# source "$ITEM_DIR/wifi.sh"
+# source "$ITEM_DIR/battery.sh"
+# source "$ITEM_DIR/keyboard.sh"
+# source "$ITEM_DIR/mic.sh"
+# source "$ITEM_DIR/volume.sh"
+source "$ITEM_DIR/kakaotalk.sh"
+source "$ITEM_DIR/mail.sh"
+# source "$ITEM_DIR/thunderbird.sh"
+source "$ITEM_DIR/svim.sh"
+source "$ITEM_DIR/messages.sh"
+source "$ITEM_DIR/toggle_stats.sh"
+source "$ITEM_DIR/separator_right.sh"
+source "$ITEM_DIR/disk.sh"
+source "$ITEM_DIR/memory.sh"
+source "$ITEM_DIR/cpu.sh"
+# source "$ITEM_DIR/network.sh"
+
+sketchybar --hotload on
+sketchybar --update
diff --git a/mac/.config/skhd/skhdrc b/mac/.config/skhd/skhdrc
new file mode 100644
index 0000000..b4a3d81
--- /dev/null
+++ b/mac/.config/skhd/skhdrc
@@ -0,0 +1,215 @@
+# open terminal
+alt - return : /Applications/Kitty.app/Contents/MacOS/kitty --single-instance -d ~ &> /dev/null
+
+# # open mpv with url from clipboard
+# shift + cmd - m : mpv $(pbpaste)
+
+# focus window
+alt - z : yabai -m window --focus recent
+alt - h : yabai -m window --focus west
+alt - j : yabai -m window --focus south
+alt - k : yabai -m window --focus north
+alt - l : yabai -m window --focus east
+alt - 0x2B : yabai -m window --focus stack.prev # alt + ,
+alt - 0x2F : yabai -m window --focus stack.next # alt + .
+
+# swap window
+alt + ctrl - z : yabai -m window --swap recent
+alt + ctrl - h : yabai -m window --swap west
+alt + ctrl - j : yabai -m window --swap south
+alt + ctrl - k : yabai -m window --swap north
+alt + ctrl - l : yabai -m window --swap east
+
+# move window
+cmd + ctrl - h : yabai -m window --warp west
+cmd + ctrl - j : yabai -m window --warp south
+cmd + ctrl - k : yabai -m window --warp north
+cmd + ctrl - l : yabai -m window --warp east
+
+# balance size of windows
+alt - 0x18 : yabai -m space --balance
+
+# toggle window float
+cmd + shift - return : yabai -m window --toggle float --grid 4:4:1:1:2:2
+# make floating window fill screen
+cmd + shift - f : yabai -m window --grid 1:1:0:0:1:4
+# make floating window fill top-left of screen
+cmd + shift - u : yabai -m window --grid 2:2:0:0:0:0
+# make floating window fill top-right of screen
+cmd + shift - i : yabai -m window --grid 2:2:1:0:0:0
+# make floating window fill bottom-left of screen
+cmd + shift - m : yabai -m window --grid 2:2:0:1:0:0
+# make floating window fill bottom-right of screen
+cmd + shift - 0x2B : yabai -m window --grid 2:2:1:1:4:4
+# make floating window fill top-half of screen
+cmd + shift - k : yabai -m window --grid 4:4:0:0:4:2
+# make floating window fill bottom-half of screen
+cmd + shift - j : yabai -m window --grid 2:2:0:1:4:4
+# make floating window fill left-half of screen
+cmd + shift - h : yabai -m window --grid 1:2:0:0:1:1
+# make floating window fill right-half of screen
+cmd + shift - l : yabai -m window --grid 1:2:1:0:1:1
+
+
+# # create desktop, move window and follow focus - uses jq for parsing json (brew install jq)
+alt + shift - w : yabai -m space --create && \
+ index="$(yabai -m query --spaces --display | jq 'map(select(."native-fullscreen" == 0))[-1].index')" && \
+ yabai -m window --space "${index}" && \
+ yabai -m space --focus "${index}"
+
+# # create desktop and follow focus - uses jq for parsing json (brew install jq)
+alt + ctrl - w : yabai -m space --create && \
+ index="$(yabai -m query --spaces --display | jq 'map(select(."native-fullscreen" == 0))[-1].index')" && \
+ yabai -m space --focus "${index}"
+
+# destroy desktop
+# cmd + alt - w : yabai -m space --destroy
+alt + ctrl - x : yabai -m space --focus prev && yabai -m space recent --destroy
+
+
+# fast focus desktop
+alt - 0x32 : yabai -m space --focus recent
+alt - 0x21 : yabai -m space --focus prev || skhd -k "ctrl + alt + cmd - p"
+alt - 0x1E : yabai -m space --focus next || skhd -k "ctrl + alt + cmd - n"
+alt - 1 : yabai -m space --focus 1 || skhd -k "ctrl + alt + cmd - 1"
+alt - 2 : yabai -m space --focus 2 || skhd -k "ctrl + alt + cmd - 2"
+alt - 3 : yabai -m space --focus 3 || skhd -k "ctrl + alt + cmd - 3"
+alt - 4 : yabai -m space --focus 4 || skhd -k "ctrl + alt + cmd - 4"
+alt - 5 : yabai -m space --focus 5 || skhd -k "ctrl + alt + cmd - 5"
+alt - 6 : yabai -m space --focus 6 || skhd -k "ctrl + alt + cmd - 6"
+alt - 7 : yabai -m space --focus 7 || skhd -k "ctrl + alt + cmd - 7"
+alt - 8 : yabai -m space --focus 8 || skhd -k "ctrl + alt + cmd - 8"
+alt - 9 : yabai -m space --focus 9 || skhd -k "ctrl + alt + cmd - 9"
+alt - 0 : yabai -m space --focus 10 || skhd -k "ctrl + alt + cmd - 0"
+alt + cmd - 1 : yabai -m space --focus 11 || skhd -k "ctrl + alt + cmd - 11"
+alt + cmd - 2 : yabai -m space --focus 12 || skhd -k "ctrl + alt + cmd - 12"
+alt + cmd - 3 : yabai -m space --focus 13 || skhd -k "ctrl + alt + cmd - 13"
+alt + cmd - 4 : yabai -m space --focus 14 || skhd -k "ctrl + alt + cmd - 14"
+alt + cmd - 5 : yabai -m space --focus 15 || skhd -k "ctrl + alt + cmd - 15"
+alt + cmd - 6 : yabai -m space --focus 16 || skhd -k "ctrl + alt + cmd - 16"
+alt + cmd - 7 : yabai -m space --focus 17 || skhd -k "ctrl + alt + cmd - 17"
+alt + cmd - 8 : yabai -m space --focus 18 || skhd -k "ctrl + alt + cmd - 18"
+alt + cmd - 9 : yabai -m space --focus 19 || skhd -k "ctrl + alt + cmd - 19"
+alt + cmd - 0 : yabai -m space --focus 20 || skhd -k "ctrl + alt + cmd - 20"
+
+# send window to desktop
+alt + ctrl - 0x21 : yabai -m window --space prev
+alt + ctrl - 0x1E : yabai -m window --space next
+alt + ctrl - 1 : yabai -m window --space 1
+alt + ctrl - 2 : yabai -m window --space 2
+alt + ctrl - 3 : yabai -m window --space 3
+alt + ctrl - 4 : yabai -m window --space 4
+alt + ctrl - 5 : yabai -m window --space 5
+alt + ctrl - 6 : yabai -m window --space 6
+alt + ctrl - 7 : yabai -m window --space 7
+alt + ctrl - 8 : yabai -m window --space 8
+alt + ctrl - 9 : yabai -m window --space 9
+alt + ctrl - 0 : yabai -m window --space 10
+
+# send window to desktop and follow focus
+alt + shift - 0x32 : yabai -m window --space recent && yabai -m space --focus recent
+alt + shift - 0x21 : yabai -m window --space prev && skhd -k "ctrl + alt + cmd - p" || yabai -m space --focus prev
+alt + shift - 0x1E : yabai -m window --space next && skhd -k "ctrl + alt + cmd - n" || yabai -m space --focus next
+alt + shift - 1 : yabai -m window --space 1 && skhd -k "ctrl + alt + cmd - 1" || yabai -m space --focus 1
+alt + shift - 2 : yabai -m window --space 2 && skhd -k "ctrl + alt + cmd - 2" || yabai -m space --focus 2
+alt + shift - 3 : yabai -m window --space 3 && skhd -k "ctrl + alt + cmd - 3" || yabai -m space --focus 3
+alt + shift - 4 : yabai -m window --space 4 && skhd -k "ctrl + alt + cmd - 4" || yabai -m space --focus 4
+alt + shift - 5 : yabai -m window --space 5 && skhd -k "ctrl + alt + cmd - 5" || yabai -m space --focus 5
+alt + shift - 6 : yabai -m window --space 6 && skhd -k "ctrl + alt + cmd - 6" || yabai -m space --focus 6
+alt + shift - 7 : yabai -m window --space 7 && skhd -k "ctrl + alt + cmd - 7" || yabai -m space --focus 7
+alt + shift - 8 : yabai -m window --space 8 && skhd -k "ctrl + alt + cmd - 8" || yabai -m space --focus 8
+alt + shift - 9 : yabai -m window --space 9 && skhd -k "ctrl + alt + cmd - 9" || yabai -m space --focus 9
+alt + shift - 0 : yabai -m window --space 10 && skhd -k "ctrl + alt + cmd - 0" || yabai -m space --focus 0
+
+# focus monitor
+cmd + ctrl - z : yabai -m display --focus recent
+cmd + ctrl - 0x21 : yabai -m display --focus prev
+cmd + ctrl - 0x1E : yabai -m display --focus next
+cmd + ctrl - 1 : yabai -m display --focus 1
+cmd + ctrl - 2 : yabai -m display --focus 2
+cmd + ctrl - 3 : yabai -m display --focus 3
+
+# send window to monitor and follow focus
+cmd + shift - r : yabai -m window --display recent && yabai -m display --focus recent
+cmd + shift - 0x21 : yabai -m window --display prev && yabai -m display --focus prev
+cmd + shift - 0x1E : yabai -m window --display next && yabai -m display --focus next
+cmd + shift - 1 : yabai -m window --display 1 && yabai -m display --focus 1
+cmd + shift - 2 : yabai -m window --display 2 && yabai -m display --focus 2
+cmd + shift - 3 : yabai -m window --display 3 && yabai -m display --focus 3
+
+# move window
+ctrl + shift - h : yabai -m window --move rel:-20:0
+ctrl + shift - j : yabai -m window --move rel:0:20
+ctrl + shift - k : yabai -m window --move rel:0:-20
+ctrl + shift - l : yabai -m window --move rel:20:0
+
+# increase window size
+alt + ctrl - a : yabai -m window --resize left:-20:0
+alt + ctrl - s : yabai -m window --resize bottom:0:20
+alt + ctrl - w : yabai -m window --resize top:0:-20
+alt + ctrl - d : yabai -m window --resize right:20:0
+
+# decrease window size
+cmd + ctrl - a : yabai -m window --resize left:20:0
+cmd + ctrl - s : yabai -m window --resize bottom:0:-20
+cmd + ctrl - w : yabai -m window --resize top:0:20
+cmd + ctrl - d : yabai -m window --resize right:-20:0
+
+# set insertion point in focused container
+cmd + ctrl - 0x7B : yabai -m window --insert west
+cmd + ctrl - 0x7D : yabai -m window --insert south
+cmd + ctrl - 0x7E : yabai -m window --insert north
+cmd + ctrl - 0x7C : yabai -m window --insert east
+cmd + ctrl - i : yabai -m window --insert stack
+
+# rotate tree
+alt - r : yabai -m space --rotate 90
+
+# mirror tree y-axis
+alt - y : yabai -m space --mirror y-axis
+
+# mirror tree x-axis
+alt - x : yabai -m space --mirror x-axis
+
+# toggle desktop offset
+alt - d : yabai -m space --toggle padding --toggle gap
+
+# toggle window parent zoom
+alt + ctrl - f : yabai -m window --toggle zoom-parent
+
+# toggle window fullscreen zoom
+alt - f : yabai -m window --toggle zoom-fullscreen
+
+# toggle window native fullscreen
+alt + shift - f : yabai -m window --toggle native-fullscreen
+
+# toggle window split type
+alt - s : yabai -m window --toggle split
+
+# float / unfloat window and restore position
+alt - t : yabai -m window --toggle float && /tmp/yabai-restore/$(yabai -m query --windows --window | jq -re '.id').restore 2>/dev/null || true
+
+# toggle sticky (show on all spaces)
+alt - 0x27 : yabai -m window --toggle sticky
+
+# toggle topmost (keep above other windows)
+alt - o : yabai -m window --toggle topmost
+
+# toggle picture-in-picture
+alt - i : yabai -m window --toggle border --toggle pip
+
+# change layout of desktop
+# alt + ctrl - 1 : yabai -m space --layout bsp
+# alt + ctrl - 2 : yabai -m space --layout float
+alt + ctrl - t : yabai -m space --layout $(yabai -m query --spaces --space | jq -r 'if .type == "bsp" then "float" else "bsp" end')
+
+###########################################################
+### --- Starting/Stopping/Restarting Yabai
+###########################################################
+# stop/start/restart yabai
+alt + cmd + ctrl - q : yabai --stop-service
+alt + cmd + ctrl - s : yabai --start-service
+alt + cmd + ctrl - r : yabai --restart-service
+alt + cmd + shift - q : skhd --stop-service
+alt + cmd + shift - s : skhd --start-service
+alt + cmd + shift - r : skhd --restart-service
diff --git a/mac/.config/vscode/argv.json b/mac/.config/vscode/argv.json
new file mode 100644
index 0000000..764fb7a
--- /dev/null
+++ b/mac/.config/vscode/argv.json
@@ -0,0 +1,20 @@
+// This configuration file allows you to pass permanent command line arguments to VS Code.
+// Only a subset of arguments is currently supported to reduce the likelihood of breaking
+// the installation.
+//
+// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT
+//
+// NOTE: Changing this file requires a restart of VS Code.
+{
+ // Use software rendering instead of hardware accelerated rendering.
+ // This can help in cases where you see rendering issues in VS Code.
+ // "disable-hardware-acceleration": true,
+
+ // Allows to disable crash reporting.
+ // Should restart the app if the value is changed.
+ "enable-crash-reporter": true,
+
+ // Unique id used for correlating crash reports sent from this instance.
+ // Do not edit this value.
+ "crash-reporter-id": "ab80ece9-6a06-484b-bd05-3861238d6615"
+} \ No newline at end of file
diff --git a/mac/.config/yabai/yabairc b/mac/.config/yabai/yabairc
new file mode 100644
index 0000000..a717336
--- /dev/null
+++ b/mac/.config/yabai/yabairc
@@ -0,0 +1,102 @@
+#!/usr/bin/env sh
+
+# necessary to load scripting-addition during startup on macOS Big Sur
+# *yabai --load-sa* is configured to run through sudo without a password
+sudo yabai --load-sa
+yabai -m signal --add event=dock_did_restart action="sudo yabai --load-sa"
+
+# mission-control desktop labels
+yabai -m space 1 --label Terminal
+yabai -m space 2 --label Code
+yabai -m space 3 --label Browser
+yabai -m space 4 --label Notes
+yabai -m space 5 --label Temp
+yabai -m space 6 --label Finder
+yabai -m space 7 --label Settings
+yabai -m space 8 --label Media
+yabai -m space 9 --label Messenger
+yabai -m space 10 --label Mail
+
+# window rules
+yabai -m rule --add app="^kitty$" space=1
+yabai -m rule --add app="^Code$" space=2
+yabai -m rule --add app="^Safari$" space=3
+yabai -m rule --add app="^Google Chrome$" space=3
+yabai -m rule --add app="^Firefox$" space=3
+yabai -m rule --add app="^(Microsoft Word|Preview|Notes|Obsidian|Anki)$" space=4
+yabai -m rule --add app="^Excel$" space=4 manage=off
+yabai -m rule --add app="^(MySQLWorkbench|DBeaver)$" space=5
+yabai -m rule --add app="^Finder$" space=6
+yabai -m rule --add app="^(universalAccessAuthWarn|Activity Monitor|Archive Utility|Alfred Preferences|Bartender 5|BetterTouchTool|Karabiner-Elements|System Settings)$" space=7
+yabai -m rule --add app="^IINA$" space=8
+yabai -m rule --add app="^Music$" space=8
+yabai -m rule --add app="^Spotify$" space=8
+yabai -m rule --add app="^Discord$" space=9 grid=1:1:0:0:1:1
+yabai -m rule --add app="^Messages$" space=9 grid=1:1:0:0:1:1
+yabai -m rule --add app="^KakaoTalk$" space=9 grid=1:1:0:0:1:1
+yabai -m rule --add app="^WeChat$" space=9 grid=1:1:0:0:1:1
+yabai -m rule --add app="^WhatsApp$" space=9 grid=1:1:0:0:1:1
+yabai -m rule --add app="^Log in$" space=9 grid=1:1:0:0:1:1 manage=off
+yabai -m rule --add app="^Mail$" space=10 grid=1:1:0:0:1:1
+yabai -m rule --add app="^Thunderbird$" space=10 grid=1:1:0:0:1:1
+yabai -m rule --add app="^FaceTime$" manage=off
+yabai -m rule --add app="^CleanShot X$" manage=off
+# yabai -m rule --add app="^mpv$" manage=off border=off sticky=on layer=above opacity=1.0 grid=8:8:6:0:2:2
+
+# signals
+yabai -m signal --add event=window_destroyed active=yes action="yabai -m query --windows --window &> /dev/null || yabai -m window --focus mouse &> /dev/null || yabai -m window --focus \$(yabai -m query --windows --space | jq .[0].id) &> /dev/null"
+yabai -m signal --add event=window_minimized active=yes action="if \$(yabai -m query --windows --window \$YABAI_WINDOW_ID | jq -r '.\"is-floating\"'); then yabai -m query --windows --window &> /dev/null || yabai -m window --focus mouse &> /dev/null || yabai -m window --focus \$(yabai -m query --windows --space | jq .[0].id) &> /dev/null; fi"
+
+# specific space settings bsp/float/stack
+yabai -m config --space 1 layout bsp
+yabai -m config --space 2 layout bsp
+yabai -m config --space 3 layout bsp
+yabai -m config --space 4 layout bsp
+yabai -m config --space 5 layout bsp
+yabai -m config --space 6 layout bsp
+yabai -m config --space 7 layout float
+yabai -m config --space 8 layout bsp
+yabai -m config --space 9 layout bsp
+yabai -m config --space 0 layout float
+
+# global settings
+yabai -m config \
+ \
+ mouse_follows_focus on \
+ focus_follows_mouse autoraise \
+ window_placement first_child \
+ window_topmost off \
+ window_shadow off \
+ window_opacity on \
+ window_opacity_duration 0.25 \
+ active_window_opacity 0.90 \
+ normal_window_opacity 0.50 \
+ window_animation_duration 0.25 \
+ insert_feedback_color 0xaad75f5f \
+ split_ratio 0.50 \
+ split_type auto \
+ auto_balance off \
+ mouse_modifier ctrl \
+ mouse_action1 move \
+ mouse_action2 resize \
+ mouse_drop_action swap \
+ layout bsp \
+ top_padding 10 \
+ left_padding 10 \
+ right_padding 10 \
+ bottom_padding 35 \
+ window_gap 10 # external_bar all:32:0 \
+
+# sketchybar
+yabai -m signal --add event=window_focused action="sketchybar --trigger window_focus"
+yabai -m signal --add event=display_added action="sleep 3 && $HOME/.config/yabai/create_spaces.sh"
+yabai -m signal --add event=display_removed action="sleep 3 && $HOME/.config/yabai/create_spaces.sh"
+yabai -m signal --add event=window_created action="sketchybar -m --trigger window_change &> /dev/null"
+yabai -m signal --add event=window_destroyed action="sketchybar -m --trigger window_change &> /dev/null"
+
+# borders
+borders \
+ style=squre \
+ active_color=0xff00ff00 \
+ width=6 \
+ hidpi=on &
diff --git a/mac/.local/bin/menu b/mac/.local/bin/menu
new file mode 100755
index 0000000..32fcfff
--- /dev/null
+++ b/mac/.local/bin/menu
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+query="switch"
+
+if [ "$query" == "switch" ]; then
+ osascript <<EOF
+tell application "System Events"
+ tell dock preferences
+ set autohide menu bar to not autohide menu bar
+ end tell
+end tell
+
+display notification "Menu bar autohide feature is toggled" with title "Menu Bar Autohide"
+EOF
+fi