diff --git a/.config/btop/btop.conf b/.config/btop/btop.conf index 9e0ee914..1db1414c 100644 --- a/.config/btop/btop.conf +++ b/.config/btop/btop.conf @@ -63,7 +63,7 @@ proc_sorting = "memory" proc_reversed = False #* Show processes as a tree. -proc_tree = False +proc_tree = True #* Use the cpu graph colors in the process list. proc_colors = True diff --git a/.config/fish/config.fish b/.config/fish/config.fish index 6e12a307..2dca25cf 100644 --- a/.config/fish/config.fish +++ b/.config/fish/config.fish @@ -1,15 +1,14 @@ -if status is-login - if not set -q __sourced_profile - set -x __sourced_profile 1 - exec bash -c "\ - test -e /etc/profile && source /etc/profile - test -e $HOME/.bash_profile && source $HOME/.bash_profile - exec fish --login - " - end +# if status is-login +# if not set -q __sourced_profile +# set -x __sourced_profile 1 +# exec bash -c "\ +# test -e /etc/profile && source /etc/profile +# test -e $HOME/.bash_profile && source $HOME/.bash_profile +# exec fish --login +# " +# end - set -e __sourced_profile -end +# end if status is-interactive # source ~/.config/fish/custom_cd.fish @@ -23,5 +22,7 @@ if status is-interactive set -gx COLORTERM truecolor set -gx TERM xterm-256color end + end set -gx EDITOR kak + diff --git a/.config/fish/fish_variables b/.config/fish/fish_variables index 56c36ec5..5190f07b 100644 --- a/.config/fish/fish_variables +++ b/.config/fish/fish_variables @@ -2,6 +2,7 @@ # VERSION: 3.0 SETUVAR --export AUTOYADMPUSH:1 SETUVAR --export EDITOR:kak +SETUVAR --export IGREP_CUSTOM_EDITOR:kak\x20\x2b\x7bline_number\x7d\x20\x7bfile_name\x7d SETUVAR --export KAKOUNE_POSIX_SHELL:/usr/bin/dash SETUVAR --export XDG_CONFIG_HOME:/home/fic/\x2econfig SETUVAR Z_DATA_DIR:/home/fic/\x2elocal/share/z diff --git a/.config/fish/functions/kak-session.fish b/.config/fish/functions/kak-session.fish index 6354a948..4fb20338 100644 --- a/.config/fish/functions/kak-session.fish +++ b/.config/fish/functions/kak-session.fish @@ -86,6 +86,8 @@ function kak-session -w kak --description "kakoune where sessions are derived fr cat $fifo >/dev/null command rm -r "$fifo_dir" + # echo "hook -once global BufCreate ^(?!.*\\*scratch\\*).* %{ delete-buffer *scratch* }" | kak -p "$session_id" + # command kak -c "$session_id" -e 'delete-buffer! *scratch*' $flags $files command kak -c "$session_id" -e 'try %{ delete-buffer *scratch*; bar-buflist; echo }' $flags $files else command kak -s "$session_id" -e "cd %[$kakroot]" $flags $files @@ -116,6 +118,8 @@ function kak-session -w kak --description "kakoune where sessions are derived fr setsid kak -d -s "$session_id" -E "cd %[$git_dir]; echo -to-file $fifo ready" & cat $fifo >/dev/null command rm -r "$fifo_dir" + # echo "hook -once global BufCreate [*]scratch[*] %{ delete-buffer *scratch* }" | kak -p "$session_id" + # command kak -c "$session_id" -e 'delete-buffer! *scratch*' $flags $files command kak -c "$session_id" -e 'try %{ delete-buffer *scratch*; bar-buflist; echo }' $flags $files else command kak -s "$session_id" -e "cd %[$git_dir]" $flags $files diff --git a/.config/kak-tree-sitter/config.toml b/.config/kak-tree-sitter/config.toml index c6283d9f..ea000905 100644 --- a/.config/kak-tree-sitter/config.toml +++ b/.config/kak-tree-sitter/config.toml @@ -96,3 +96,47 @@ pin = "7275b7f85014aad7e15d4987ec4f2249572eecfb" [language.ini.queries] path = "runtime/queries/ini" +[language.just] +aliases = ["justfile"] + +[language.just.grammar.source.git] +url = "https://github.com/poliorcetics/tree-sitter-just" +pin = "8d03cfdd7ab89ff76d935827de1b93450fa0ec0a" + +[language.just.grammar] +path = "src" +compile = "cc" +compile_args = ["-c", "-fpic", "../parser.c", "-I", ".."] +compile_flags = ["-O3"] +link = "cc" +link_args = ["-shared", "-fpic", "parser.o", "-o", "just.so"] +link_flags = ["-O3"] + +[language.just.queries.source.git] +url = "https://github.com/helix-editor/helix" +pin = "f6878f62f74430cff188e7978d06c5ed143179e9" + +[language.just.queries] +path = "runtime/queries/just" + +# typescript +[language.typescript.grammar.source.git] +url = "https://github.com/tree-sitter/tree-sitter-typescript" +pin = "b1bf4825d9eaa0f3bdeb1e52f099533328acfbdf" + +[language.typescript.grammar] +path = "typescript/src" +compile = "cc" +compile_args = ["-c", "-fpic", "../scanner.c", "../parser.c", "-I", ".."] +compile_flags = ["-O3"] +link = "cc" +link_args = ["-shared", "-fpic", "scanner.o", "parser.o", "-o", "typescript.so"] +link_flags = ["-O3"] + +[language.typescript.queries.source.git] +url = "https://git.sr.ht/~ficd/kak-tree-sitter" +pin = "d8afa2ddcf2f97d29f495eccc08ad9ccff8d9199" + +[language.typescript.queries] +path = "runtime/queries/typescript" + diff --git a/.config/kak/autoload/clipboard.kak b/.config/kak/autoload/clipboard.kak index c3a3cf1d..9c735a44 100644 --- a/.config/kak/autoload/clipboard.kak +++ b/.config/kak/autoload/clipboard.kak @@ -1,4 +1,56 @@ -map -docstring "yank the selection into the clipboard" global user y " wl-copy" +declare-option -docstring %{ + Command for copying to system clipboard. +} str clipboard_copy_cmd 'wl-copy' + +declare-option int clip_selcount 0 + +define-command -hidden clip-trim %{ + try %{ + execute-keys \n + execute-keys bjGjd + } +} + +define-command -docstring %{ + clip-copy [-split]: copy selections to system clipboard + Set the clipboard_copy_cmd option to change the command + Switches: + -split ensure each selection separated by newline +} -params 0..1 clipboard-copy %{ + # preserve registers + evaluate-commands -save-regs 'a|' %{ + set-option local clip_selcount %val{selection_count} + # copy selections + execute-keys '"ay' + # set shell register to copy command + set-register | %opt{clipboard_copy_cmd} + # branch based on switch + execute-keys %sh{ + if [ "$kak_opt_clip_selcount" -gt 1 ]; then + echo ': edit -scratch' + if [ ${#} = 1 ] && [ ${1} = '-split' ]; then + # paste all + # reduce selections to those without newline + # append a newline + # delete extra newlines + # select all, pipe to copy cmd + echo '"a\na' + echo 'gj: clip-trim' + echo '%' + else + # paste all, select all, pipe to copy cmd + echo '"a%' + fi + echo ": delete-buffer" + else + echo '' + fi + } + } +} + +map -docstring "yank the selections into the clipboard" global user y ": clipboard-copy" +map -docstring "yank the split selections into the clipboard" global user Y ": clipboard-copy -split" map -docstring "paste the clipboard" global user p " wl-paste -n" map -docstring "paste the clipboard before" global user P "! wl-paste -n" diff --git a/.config/kak/autoload/detection/editorconfig.kak b/.config/kak/autoload/detection/editorconfig.kak new file mode 100644 index 00000000..5d1b1711 --- /dev/null +++ b/.config/kak/autoload/detection/editorconfig.kak @@ -0,0 +1,106 @@ +# http://editorconfig.org/#file-format-details +# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + +# Patch by Daniel : +# Support editorconfig for scratch buffers. +# If we're in a scratch buffer, we check the filetype, +# create a temp file with the appropriate extension, and pass +# it to editorconfig. If there's no .editorconfig in /tmp, +# we copy the one from $HOME. We clean up the files after. + +# Detection +# ‾‾‾‾‾‾‾‾‾ + +hook global BufCreate .*[.](editorconfig) %{ + set-option buffer filetype ini + set-option buffer static_words indent_style indent_size tab_width \ + end_of_line charset insert_final_newline trim_trailing_whitespace root \ + latin1 utf-8 utf-8-bom utf-16be utf-16le lf cr crlf unset space tab max_line_length +} + +define-command editorconfig-load -params ..1 -docstring "editorconfig-load [file]: set formatting behavior according to editorconfig" %{ + evaluate-commands %sh{ + command -v editorconfig >/dev/null 2>&1 || { echo "fail editorconfig could not be found"; exit 1; } + + file="${1:-$kak_buffile}" + filetype="$kak_opt_filetype" + # check if we have a real file or a scratch buffer + if [ ! -f "$file" ]; then + scratch="true" + # need to create .editorconfig in /tmp if it's not there + if [ ! -f "/tmp/.editorconfig" ]; then + cp "$HOME/.editorconfig" "/tmp/.editorconfig" + fi + case "$filetype" in + markdown) + file="/tmp/scratch.md" + touch "$file";; + python) + file="/tmp/scratch.py" + touch "$file";; + sh) + file="/tmp/scratch.sh" + touch "$file";; + bash) + file="/tmp/scratch.sh" + touch "$file";; + fish) + file="/tmp/scratch.fish" + touch "$file";; + json) + file="/tmp/scratch.json" + touch "$file";; + toml) + file="/tmp/scratch.toml" + touch "$file";; + *) + file="/tmp/scratch.txt" + touch "$file";; + esac + fi + case $file in + /*) # $kak_buffile is a full path that starts with a '/' + printf %s\\n "remove-hooks buffer editorconfig-hooks" + editorconfig "$file" | awk -v file="$file" -F= -- ' + $1 == "indent_style" { indent_style = $2 } + $1 == "indent_size" { indent_size = $2 == "tab" ? 4 : $2 } + $1 == "tab_width" { tab_width = $2 } + $1 == "end_of_line" { end_of_line = $2 } + $1 == "charset" { charset = $2 } + $1 == "trim_trailing_whitespace" { trim_trailing_whitespace = $2 } + $1 == "max_line_length" { max_line_length = $2 } + + END { + if (indent_style == "tab") { + print "set-option buffer indentwidth 0" + } + if (indent_style == "space") { + print "set-option buffer indentwidth " indent_size + } + if (indent_size || tab_width) { + print "set-option buffer tabstop " (tab_width ? tab_width : indent_size) + } + if (end_of_line == "lf" || end_of_line == "crlf") { + print "set-option buffer eolformat " end_of_line + } + if (charset == "utf-8-bom") { + print "set-option buffer BOM utf8" + } + if (trim_trailing_whitespace == "true") { + print "hook buffer BufWritePre \"" file "\" -group editorconfig-hooks %{ try %{ execute-keys -draft %{%s\\h+$d} } }" + } + if (max_line_length && max_line_length != "off") { + print "set window autowrap_column " max_line_length + print "autowrap-enable" + print "add-highlighter window/ column %sh{ echo $((" max_line_length "+1)) } default,bright-black" + } + } + ' ;; + esac + if [ -n "$scratch" ]; then + rm "/tmp/.editorconfig" + rm "$file" + fi + } +} +complete-command editorconfig-load file diff --git a/.config/kak/autoload/filetype.kak b/.config/kak/autoload/filetype.kak index b1d3093c..f177f806 100644 --- a/.config/kak/autoload/filetype.kak +++ b/.config/kak/autoload/filetype.kak @@ -21,19 +21,19 @@ hook global WinSetOption filetype=kak %{ hook global WinSetOption filetype=typst %{ set-option buffer formatcmd "typstyle --wrap-text" hook -group typst-auto-format window BufWritePre .* format - hook -once -always WinSetOption filetype=.* %{ + hook -once -always window WinSetOption filetype=.* %{ unset-option window formatcmd remove-hooks window typst-auto-format } - define-command -docstring %{ - Spawns a Zathura pdf preview and Typst watcher for the currently open Typst file - } typst %{ - nop %sh{ - { - "$kak_config/scripts/kak-typ-zathura.fish" -k -w "$kak_buffile" "$kak_client_pid" - } > /dev/null 2>&1 < /dev/null & - } - } + define-command -docstring %{ + Spawns a Zathura pdf preview and Typst watcher for the currently open Typst file + } typst %{ + nop %sh{ + { + "$kak_config/scripts/kak-typ-zathura.fish" -k -w "$kak_buffile" "$kak_client_pid" + } > /dev/null 2>&1 < /dev/null & + } + } } hook global WinSetOption filetype=fish %{ diff --git a/.config/kak/autoload/filetype/just.kak b/.config/kak/autoload/filetype/just.kak new file mode 100644 index 00000000..5757aee1 --- /dev/null +++ b/.config/kak/autoload/filetype/just.kak @@ -0,0 +1,83 @@ +# Detection +# ‾‾‾‾‾‾‾‾‾ + +hook global BufCreate .*/?[jJ]ustfile %{ + set-option buffer filetype justfile +} + +hook global WinSetOption tree_sitter_lang=justfile %{ + set-option window tree_sitter_lang just +} + +hook global WinSetOption filetype=justfile %{ + require-module justfile + + hook window ModeChange pop:insert:.* -group justfile-trim-indent justfile-trim-indent + hook window InsertChar \n -group justfile-insert just-insert-on-new-line + hook window InsertChar \n -group justfile-indent just-indent-on-new-line + hook -once -always window WinSetOption filetype=.* %{ remove-hooks window justfile-.+ } +} + +hook -group justfile-highlight global WinSetOption filetype=justfile %{ + add-highlighter window/justfile ref justfile + hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/justfile } +} + + +provide-module justfile %{ + +# Indentation +# ‾‾‾‾‾‾‾‾‾‾‾ + +define-command -hidden justfile-trim-indent %{ + evaluate-commands -no-hooks -draft -itersel %{ + execute-keys x + # remove trailing white spaces + try %{ execute-keys -draft s \h + $ d } + } +} + +define-command -hidden just-insert-on-new-line %{ + # copy '#' comment prefix and following white spaces + try %{ execute-keys -draft k x s ^\h*//\h* y jgh P } +} + +define-command -hidden just-indent-on-new-line %{ + evaluate-commands -draft -itersel %{ + # preserve previous line indent + try %{ execute-keys -draft K } + # cleanup trailing white spaces on previous line + try %{ execute-keys -draft kx s \h+$ "_d } + } +} + +# Highlighters +# ‾‾‾‾‾‾‾‾‾‾‾‾ + +add-highlighter shared/justfile regions + +add-highlighter shared/justfile/content default-region group +add-highlighter shared/justfile/content/recipe regex '^@?([\w-]+)([^\n]*):(?!=)([^\n]*)' 1:function 2:meta 3:keyword +add-highlighter shared/justfile/content/assignments regex ^([\w-]+\h*:=\h*[^\n]*) 1:meta +add-highlighter shared/justfile/content/operator regex '((^@|:=|=|\+|\(|\)))' 1:operator +add-highlighter shared/justfile/content/strings regions +add-highlighter shared/justfile/content/strings/double region '"' (? ': trigger-user-hook donehop' map window normal , ': trigger-user-hook donehop,' hook -once window User donehop %{ + hop-clear-cursor eval "ui-scrolloff-enable" try %{ ui-wrap-enable } unmap window normal @@ -37,3 +76,4 @@ map -docstring %{ map -docstring %{ Hop mode } global normal ': enter-user-mode hop' +~ diff --git a/.config/kak/autoload/lsp.kak b/.config/kak/autoload/lsp.kak index fd17d343..07156284 100644 --- a/.config/kak/autoload/lsp.kak +++ b/.config/kak/autoload/lsp.kak @@ -1,5 +1,6 @@ # load plugin eval %sh{kak-lsp} +# eval %sh{kak-lsp-diags} # mappings map global user l ': enter-user-mode lsp' -docstring 'LSP mode' @@ -24,7 +25,7 @@ define-command -hidden -override lsp-hide-code-actions %{ set-option global lsp_debug false # list of filetypes for which LSP will be activated in the window scope -declare-option str-list lsp_filetypes python go rust bash fish c cpp typst markdown yaml json jsonc bash sh +declare-option str-list lsp_filetypes python go rust bash fish c cpp typst markdown yaml json jsonc bash sh typescript javascript latex declare-option -hidden bool inlay_enabled false @@ -48,21 +49,40 @@ define-command -hidden inlay-toggle %{ } } -declare-option -hidden bool diagnostics_enabled false -define-command -hidden diagnostics-on %{ +declare-option -hidden bool inlay_diagnostics_enabled false +define-command -hidden inlay-diagnostics-on %{ lsp-inlay-diagnostics-enable window - set-option window diagnostics_enabled true + set-option window inlay_diagnostics_enabled true } -define-command -hidden diagnostics-off %{ +define-command -hidden inlay-diagnostics-off %{ lsp-inlay-diagnostics-disable window - set-option window diagnostics_enabled false + set-option window inlay_diagnostics_enabled false } -define-command -hidden diagnostics-toggle %{ +define-command -hidden inlay-diagnostics-toggle %{ evaluate-commands %sh{ - if [ "$kak_opt_diagnostics_enabled" = "true" ]; then - echo "diagnostics-off" + if [ "$kak_opt_inlay_diagnostics_enabled" = "true" ]; then + echo "inlay-diagnostics-off" else - echo "diagnostics-on" + echo "inlay-diagnostics-on" + fi + } +} + +declare-option -hidden bool inline_diagnostics_enabled true +define-command -hidden inline-diagnostics-on %{ + lsp-inline-diagnostics-enable window + set-option window inline_diagnostics_enabled true +} +define-command -hidden inline-diagnostics-off %{ + lsp-inline-diagnostics-disable window + set-option window inline_diagnostics_enabled false +} +define-command inline-diagnostics-toggle %{ + evaluate-commands %sh{ + if [ "$kak_opt_inline_diagnostics_enabled" = "true" ]; then + echo "inline-diagnostics-off" + else + echo "inline-diagnostics-on" fi } } @@ -76,14 +96,11 @@ define-command -hidden lsp-filetype-hooks-update %{ # commands to execute for lsp window settings lsp-enable-window inlay-on - try %{ - # only map to UI mode if that module is available - map -docstring 'toggle inlay hints' window ui h ': inlay-toggle' - map -docstring 'toggle inlay diagnostics' window ui d ': diagnostics-toggle' - } catch %{ - map -docstring 'toggle inlay hints' window lsp ': inlay-toggle' - map -docstring 'toggle inlay diagnostics' window lsp ': diagnostics-toggle' - } + # only map to UI mode if that module is available + map -docstring 'toggle inlay hints' window ui h ': inlay-toggle' + map -docstring 'toggle inlay diagnostics' window ui d ': inlay-diagnostics-toggle' + map -docstring 'toggle inline diagnostics' window ui e ': inline-diagnostics-toggle' + trigger-user-hook lsp-enabled } } lsp-filetype-hooks-update @@ -102,7 +119,51 @@ hook -group lsp-filetype-python global BufSetOption filetype=python %{ [basedpyright-langserver.settings.basedpyright.analysis] typeCheckingMode = "standard" inlayHints.genericTypes = true + } +} +remove-hooks global lsp-filetype-latex +hook -group lsp-filetype-latex global BufSetOption filetype=latex %{ + set-option buffer lsp_servers %{ + [texlab] + root_globs = [".git", ".hg"] + [texlab.settings.texlab] + # See https://github.com/latex-lsp/texlab/wiki/Configuration + # + # Preview configuration for zathura with SyncTeX search. + # For other PDF viewers see https://github.com/latex-lsp/texlab/wiki/Previewing + build.onSave = true + forwardSearch.executable = "zathura" + forwardSearch.args = [ + "%p", + "--synctex-forward", # Support texlab-forward-search + "%l:1:%f", + "--synctex-editor-command", # Inverse search: use Control+Left-Mouse-Button to jump to source. + """ + sh -c ' + echo " + evaluate-commands -client %%opt{texlab_client} %%{ + evaluate-commands -try-client %%opt{jumpclient} %%{ + edit -- %%{input} %%{line} + } + } + " | kak -p $kak_session + ' + """, + ] + } +} + +remove-hooks global lsp-filetype-javascript +hook -group lsp-filetype-javascript global BufSetOption filetype=(?:javascript|typescript) %{ + set-option buffer lsp_servers %{ + [typescript-language-server] + root_globs = ["package.json", "tsconfig.json", "jsconfig.json", ".git", ".hg"] + args = ["--stdio"] + settings_section = "_" + [typescript-language-server.settings._] + # quotePreference = "double" + # typescript.format.semicolons = "insert" } } @@ -115,3 +176,69 @@ hook -group lsp-filetype-fish global BufSetOption filetype=fish %{ command = "/home/fic/.config/kak/scripts/fish-lsp.fish" } } + +remove-hooks global lsp-filetype-markdown +hook -group lsp-filetype-markdown global BufSetOption filetype=markdown %{ + set-option buffer lsp_servers %{ + [marksman] + root_globs = [".marksman.toml", ".git"] + args = ["server"] + + [harper-ls] + root_globs = ["*"] + args = ["--stdio"] + command = "harper-ls" + [harper-ls.settings.harper-ls.linters] + LongSentences = false + } +} + +remove-hooks global lsp-filetype-typst +hook -group lsp-filetype-typst global BufSetOption filetype=typst %{ + set-option buffer lsp_servers %{ + [tinymist] + root_globs = [".git", ".hg"] + args = ["lsp"] + settings_section = "_" + [tinymist.settings._] + # See https://myriad-dreamin.github.io/tinymist/configurations.html + exportPdf = "never" + # exportPdf = "onDocumentHasTitle" + formatterMode = "typstyle" + previewFeature = "disable" + + [harper-ls] + root_globs = ["*"] + args = ["--stdio"] + command = "harper-ls" + [harper-ls.settings.harper-ls.linters] + LongSentences = false + } + set-option -add buffer lsp_servers "formatterPrintWidth = %opt{autowrap_column}" +} + +# # can be empty, global, or file +# declare-option -hidden str harper_add "" +# define-command -hidden harper-add -params 1 %{ +# set-option window harper_add %arg{1} +# lsp-code-actions -auto-single +# } + +# # can override this to customize what's rendered in the menu +# # Each code action is two args: its name, and the corresponding command +# define-command -override -hidden lsp-perform-code-action -params 1.. -docstring "Called on :lsp-code-actions" %{ +# evaluate-commands %sh{ +# # harper specific filtering +# if printf '%s' "$kak_opt_lsp_servers" | grep -q 'harper-ls'; then +# if [ "$kak_opt_harper_add" = "global" ]; then +# # filter and keep only +# echo "echo -debug 'harper adding global'" +# fi +# fi +# echo "echo -debug dumping $# code actions args:" +# echo "echo -debug %arg{@}" + +# } +# lsp-menu %arg{@} +# } + diff --git a/.config/kak/autoload/notes.kak b/.config/kak/autoload/notes.kak new file mode 100644 index 00000000..4469adac --- /dev/null +++ b/.config/kak/autoload/notes.kak @@ -0,0 +1,287 @@ +provide-module notes %~ +# Global directory for notes. +declare-option str notes_root_dir "%sh{ echo $HOME/notes/kak }" + +# Active directory. +# +# Global directory (`notes_root_dir`) or a local override. +declare-option str notes_active_dir "%opt{notes_root_dir}" + +declare-option str notes_sym_todo 'TODO' +declare-option str notes_sym_wip 'WIP' +declare-option str notes_sym_done 'DONE' +declare-option str notes_sym_wontdo 'WONTDO' +declare-option str notes_sym_idea 'IDEA' +declare-option str notes_sym_question 'QUESTION' +declare-option str notes_sym_hold 'HOLD' +declare-option str notes_sym_review 'REVIEW' +declare-option str notes_find 'fd -t file .md' +declare-option -hidden str notes_tasks_list_current_line +declare-option -hidden str notes_journal_now + +# Main notes mode. +declare-user-mode notes + +# Mode to edit tasks. +declare-user-mode notes-tasks + +# Mode to list tasks. +declare-user-mode notes-tasks-list + +# Mode to navigate journal. +declare-user-mode notes-journal-nav + +# Mode to navigate journal (last journals). +declare-user-mode notes-journal-nav-last + +set-face global notes_todo green +set-face global notes_wip blue +set-face global notes_done black +set-face global notes_wontdo black +set-face global notes_idea green +set-face global notes_question cyan +set-face global notes_hold red +set-face global notes_review yellow + +set-face global notes_issue cyan+u +set-face global notes_subtask_uncheck green +set-face global notes_subtask_check black +set-face global notes_tag blue+i + +# Open the daily journal. +define-command notes-journal-open -docstring 'open daily journal' %{ + nop %sh{ + mkdir -p "$kak_opt_notes_active_dir/journal/$(date +%Y/%b)" + } + + evaluate-commands %{ + edit "%opt{notes_active_dir}/journal/%sh{ date '+%Y/%b/%a %d' }.md" + set-option buffer notes_journal_now %sh{ date } + } +} + +# Open a journal relative to today. +define-command -hidden notes-journal-open-rel -params -1 %{ + nop %sh{ + mkdir -p "$kak_opt_notes_active_dir/journal/$(date -d ""$kak_opt_notes_journal_now $1"" +%Y/%b)" + } + + evaluate-commands %{ + edit -existing "%opt{notes_active_dir}/journal/%sh{ date -d ""$kak_opt_notes_journal_now $1"" ""+%Y/%b/%a %d"" }.md" + set-option buffer notes_journal_now %sh{ date -d """$kak_opt_notes_journal_now $1""" } + } +} + +# Open a note by prompting the user with a menu. +define-command notes-open -docstring 'open note' %{ + prompt -menu -shell-script-candidates "$kak_opt_notes_find $kak_opt_notes_active_dir/notes" 'open note:' %{ + edit %sh{ + echo "${kak_text%.md}.md" + } + } +} + +# Create a new note by prompting the user for its text. +define-command notes-new-note -docstring 'new note' %{ + prompt note: %{ + edit %sh{ + echo "$kak_opt_notes_active_dir/notes/${kak_text%.md}.md" + } + } +} + +# Archive a note by prompting the user for which note to operate on. +define-command notes-archive-note -docstring 'archive note' %{ + prompt -menu -shell-script-candidates "$kak_opt_notes_find $kak_opt_notes_active_dir/notes" archive: %{ + nop %sh{ + mkdir -p "$kak_opt_notes_active_dir/archives" + mv "$kak_text" "$kak_opt_notes_active_dir/archives/" + } + } +} + +# Prompt the user to pick and open an archived note. +define-command notes-archive-open -docstring 'open archive' %{ + prompt -menu -shell-script-candidates "$kak_opt_notes_find $kak_opt_notes_active_dir/archives" 'open archive:' %{ + edit %sh{ + echo "${kak_text%.md}.md" + } + } +} + +# Capture a new note. +define-command notes-capture -docstring 'capture' %{ + prompt capture: %{ + nop %sh{ + echo -e "> $(date '+%a %b %d %Y, %H:%M:%S')\n$kak_text\n" >> "$kak_opt_notes_active_dir/capture.md" + } + } +} + +# Open the capture file. +define-command notes-open-capture -docstring 'open capture' %{ + edit "%opt{notes_active_dir}/capture.md" +} + +# Switch the status of a note to the input parameter. +define-command notes-task-switch-status -params 1 -docstring 'switch task' %{ + execute-keys -draft "gife_c%arg{1}" +} + +# Open a GitHub issue. This requires a specific formatting of the file. +define-command notes-task-gh-open-issue -docstring 'open GitHub issue' %{ + evaluate-commands -save-regs 'il' %{ + try %{ + execute-keys -draft 'w"iy' + execute-keys -draft '%sgithub_project: ;_"ly' + nop %sh{ + open "https://github.com/$kak_reg_l/issues/$kak_reg_i" + } + } + } +} + +define-command -hidden notes-tasks-list-by-regex -params 1 -docstring 'list tasks by status' %{ + edit -scratch *notes-tasks-list* + unset-option buffer notes_tasks_list_current_line + execute-keys "%%d|rg -n --column -e '%arg{1}' '%opt{notes_active_dir}/notes' '%opt{notes_active_dir}/journal' '%opt{notes_active_dir}/capture.md'|sortgg" +} + +# List all tasks. +define-command notes-tasks-list-all -docstring 'list all tasks' %{ + notes-tasks-list-by-regex "%opt{notes_sym_todo}|%opt{notes_sym_wip}|%opt{notes_sym_done}|%opt{notes_sym_wontdo}|%opt{notes_sym_idea}|%opt{notes_sym_question}|%opt{notes_sym_hold}" +} + +# Command executed when pressing in a *notes-tasks-list* buffer. +define-command -hidden notes-tasks-list-open %{ + set-option buffer notes_tasks_list_current_line %val{cursor_line} + execute-keys -with-hooks -save-regs 'flc' 'giT:"fyllT:"lyllT:"cy:edit "%reg{f}" %reg{l} %reg{c}' +} + +# Run a grepper with the provided arguments as search query. +define-command -hidden notes-grepcmd -params 2 %{ + # Initial implementation based on rg . + execute-keys ":grep %arg{2} %arg{1}" +} + +# Prompt the user for terms to search in notes, journals, archives and the +# capture file. +define-command notes-search -docstring 'search notes' %{ + prompt 'search notes:' %{ + notes-grepcmd "%opt{notes_active_dir}" "%val{text}" + } +} + +# Synchronize notes remotely. +define-command notes-sync -docstring 'synchronize notes' %{ + # First, we always check-in new modifications; then, we check whether we have anything else to send + info -title 'notes' 'starting synchronizing…' + + nop %sh{ + cd $kak_opt_notes_active_dir + git fetch --prune origin + git rebase --autostash origin/master + git add -A . + git commit -m "$(date +'Sync update %a %b %d %Y')" + git push origin + } + + info -title 'notes' 'finished synchronizing' +} + +# Toggle overriding the active directory with pwd and vice versa. +define-command notes-override-active-dir -docstring 'override the active directory with PWD' %{ + set-option global notes_active_dir %sh{ + dir=$(pwd) + if [ "$kak_opt_notes_active_dir" == "$dir" ]; then + echo "$kak_opt_notes_root_dir" + else + echo "$dir" + fi + } + + info -title notes "active dir: %opt{notes_active_dir}" +} + +add-highlighter shared/notes-tasks group +add-highlighter shared/notes-tasks/todo regex "(%opt{notes_sym_todo})" 1:notes_todo +add-highlighter shared/notes-tasks/wip regex "(%opt{notes_sym_wip})" 1:notes_wip +add-highlighter shared/notes-tasks/done regex "(%opt{notes_sym_done})" 1:notes_done +add-highlighter shared/notes-tasks/wontdo regex "(%opt{notes_sym_wontdo})" 1:notes_wontdo +add-highlighter shared/notes-tasks/idea regex "(%opt{notes_sym_idea})" 1:notes_idea +add-highlighter shared/notes-tasks/question regex "(%opt{notes_sym_question})" 1:notes_question +add-highlighter shared/notes-tasks/hold regex "(%opt{notes_sym_hold})" 1:notes_hold +add-highlighter shared/notes-tasks/review regex "(%opt{notes_sym_review})" 1:notes_review +add-highlighter shared/notes-tasks/issue regex " (#[0-9]+)" 1:notes_issue +add-highlighter shared/notes-tasks/subtask-uncheck regex "-\s* (\[ \])[^\n]*" 1:notes_subtask_uncheck +add-highlighter shared/notes-tasks/subtask-check regex "-\s* (\[x\])\s*([^\n]*)"\ + 1:notes_subtask_check + +add-highlighter shared/notes-tasks-list group +add-highlighter shared/notes-tasks-list/path regex "^((?:\w:)?[^:\n]+):(\d+):(\d+)?" 1:green 2:blue 3:blue +add-highlighter shared/notes-tasks-list/current-line line %{%opt{notes_tasks_list_current_line}} default+b + +map global user o ':enter-user-mode notes' -docstring 'notes' + +map global notes A ':notes-archive-note' -docstring 'archive note' +map global notes a ':notes-archive-open' -docstring 'open archived note' +map global notes C ':notes-capture' -docstring 'capture' +map global notes c ':notes-open-capture' -docstring 'open capture' +map global notes j ':notes-journal-open' -docstring 'open journal' +map global notes J ':enter-user-mode notes-journal-nav' -docstring 'navigate journals' +map global notes l ':enter-user-mode notes-tasks-list' -docstring 'tasks list' +map global notes N ':notes-new-note' -docstring 'new note' +map global notes n ':notes-open' -docstring 'open note' +map global notes / ':notes-search' -docstring 'search in notes' +map global notes S ':notes-sync' -docstring 'synchronize notes' +map global notes t ':enter-user-mode notes-tasks' -docstring 'tasks' +map global notes z ':notes-override-active-dir' -docstring 'switch notes dir with PWD' + +map global notes-journal-nav l ':enter-user-mode notes-journal-nav-last' -docstring 'last…' +map global notes-journal-nav d ':notes-journal-open-rel "-1 day"' -docstring 'day before' +map global notes-journal-nav D ':notes-journal-open-rel "+1 day"' -docstring 'day after' +map global notes-journal-nav w ':notes-journal-open-rel "-1 week"' -docstring 'week before' +map global notes-journal-nav W ':notes-journal-open-rel "+1 week"' -docstring 'week after' +map global notes-journal-nav m ':notes-journal-open-rel "-1 month"' -docstring 'month before' +map global notes-journal-nav M ':notes-journal-open-rel "+1 month"' -docstring 'month after' + +map global notes-journal-nav-last m ':notes-journal-open-rel "last monday"' -docstring 'monday' +map global notes-journal-nav-last t ':notes-journal-open-rel "last tuesday"' -docstring 'tuesday' +map global notes-journal-nav-last w ':notes-journal-open-rel "last wednesday"' -docstring 'wednesday' +map global notes-journal-nav-last h ':notes-journal-open-rel "last thursday"' -docstring 'thursday' +map global notes-journal-nav-last f ':notes-journal-open-rel "last friday"' -docstring 'friday' +map global notes-journal-nav-last T ':notes-journal-open-rel "last saturday"' -docstring 'saturday' +map global notes-journal-nav-last S ':notes-journal-open-rel "last sunday"' -docstring 'sunday' + +map global notes-tasks-list a ":notes-tasks-list-all" -docstring 'list all tasks' +map global notes-tasks-list d ":notes-tasks-list-by-regex %opt{notes_sym_done}" -docstring 'list done tasks' +map global notes-tasks-list h ":notes-tasks-list-by-regex %opt{notes_sym_hold}" -docstring 'list hold tasks' +map global notes-tasks-list i ":notes-tasks-list-by-regex %opt{notes_sym_idea}" -docstring 'list ideas' +map global notes-tasks-list l ":notes-tasks-list-by-regex '\ :[^:]+:'" -docstring 'list tasks by labels' +map global notes-tasks-list n ":notes-tasks-list-by-regex %opt{notes_sym_wontdo}" -docstring 'list wontdo tasks' +map global notes-tasks-list q ":notes-tasks-list-by-regex %opt{notes_sym_question}" -docstring 'list questions' +map global notes-tasks-list r ":notes-tasks-list-by-regex %opt{notes_sym_review}" -docstring 'list reviews' +map global notes-tasks-list t ":notes-tasks-list-by-regex %opt{notes_sym_todo}" -docstring 'list todo tasks' +map global notes-tasks-list w ":notes-tasks-list-by-regex %opt{notes_sym_wip}" -docstring 'list wip tasks' + +hook -group notes-tasks global WinCreate \*notes-tasks-list\* %{ + map buffer normal '' ':notes-tasks-list-open' + add-highlighter window/ ref notes-tasks + add-highlighter window/ ref notes-tasks-list +} + +hook -group notes-tasks global WinCreate .*\.md %{ + add-highlighter window/ ref notes-tasks + + map window notes-tasks d ":notes-task-switch-status %opt{notes_sym_done}" -docstring 'switch task to done' + map window notes-tasks h ":notes-task-switch-status %opt{notes_sym_hold}" -docstring 'switch task to hold' + map window notes-tasks i ":notes-task-switch-status %opt{notes_sym_idea}" -docstring 'switch task to idea' + map window notes-tasks n ":notes-task-switch-status %opt{notes_sym_wontdo}" -docstring 'switch task to wontdo' + map window notes-tasks q ":notes-task-switch-status %opt{notes_sym_question}" -docstring 'switch task to question' + map window notes-tasks ":notes-task-gh-open-issue" -docstring 'open GitHub issue' + map window notes-tasks r ":notes-task-switch-status %opt{notes_sym_review}" -docstring 'switch task to review' + map window notes-tasks t ":notes-task-switch-status %opt{notes_sym_todo}" -docstring 'switch task to todo' + map window notes-tasks w ":notes-task-switch-status %opt{notes_sym_wip}" -docstring 'switch task to wip' +} + +~ diff --git a/.config/kak/autoload/plugins.kak b/.config/kak/autoload/plugins.kak index 4fd24c62..399bae9d 100644 --- a/.config/kak/autoload/plugins.kak +++ b/.config/kak/autoload/plugins.kak @@ -95,7 +95,7 @@ bundle-noload kakoune-text-objects https://github.com/Delapouite/kakoune-text-ob bundle smarttab.kak https://github.com/andreyorst/smarttab.kak %{ require-module smarttab set-option global softtabstop 2 - hook global BufCreate .* %{ + hook global WinSetOption filetype=.* %{ editorconfig-load autoconfigtab } diff --git a/.config/kak/autoload/stdlib/detection/editorconfig.kak b/.config/kak/autoload/stdlib/detection/editorconfig.kak deleted file mode 120000 index cebb754c..00000000 --- a/.config/kak/autoload/stdlib/detection/editorconfig.kak +++ /dev/null @@ -1 +0,0 @@ -/usr/share/kak/rc/detection/editorconfig.kak \ No newline at end of file diff --git a/.config/kak/autoload/stdlib/filetype/just.kak b/.config/kak/autoload/stdlib/filetype/just.kak deleted file mode 120000 index 60c8fa04..00000000 --- a/.config/kak/autoload/stdlib/filetype/just.kak +++ /dev/null @@ -1 +0,0 @@ -/usr/share/kak/rc/filetype/just.kak \ No newline at end of file diff --git a/.config/kak/kakrc b/.config/kak/kakrc index 68dcb779..f673bb65 100644 --- a/.config/kak/kakrc +++ b/.config/kak/kakrc @@ -1,12 +1,14 @@ evaluate-commands %sh{ kak-tree-sitter -dks --init $kak_session } evaluate-commands %sh{kak-popup init} colorscheme ashen +require-module hop-kak require-module fishr require-module surround require-module ficgrep require-module byline require-module spell require-module title-bar +require-module notes set-option global scrolloff 3,3 require-module ui-mode @@ -93,25 +95,16 @@ map -docstring 'case insensitive backward extend-search' global user '' L" map -docstring 'Extend to file end' global user n "gjl" -# Zathura pdf preview only for Typst files - -hook global WinSetOption filetype=typst %{ - define-command -docstring %{ - Spawns a Zathura pdf preview and Typst watcher for the currently open Typst file - } typst %{ - nop %sh{ - { - "$kak_config/scripts/kak-typ-zathura.fish" -k -w "$kak_buffile" "$kak_client_pid" - } > /dev/null 2>&1 < /dev/null & - } - } -} - define-command -docstring "Create a scratch buffer" scratch %{ edit -scratch } + alias global s scratch +define-command -params 1 -docstring "Set buffer filetype" filetype %{ + set-option buffer filetype %arg{1} +} + define-command -docstring "New terminal in cwd" cwd-terminal %{ terminal fish } @@ -156,12 +149,21 @@ define-command -override -hidden consume %{ } } +define-command terminal-consume %{ + terminal dash -c 'niri msg action consume-or-expel-window-left; exec fish' +} + +alias global tc terminal-consume + define-command -docstring %{ Create a new client in vertial split -} newv %{ +} new-consume %{ new consume } +alias global nc new-consume +alias global n new + # jumplist map -docstring 'jump forward' global normal @@ -169,3 +171,22 @@ map -docstring 'save to jumplist' global normal # selection saving map -docstring 'add selection' global normal Y a + +define-command -docstring 'open popup shell' popup-shell %{ + popup fish +} +alias global pp popup-shell + +map -docstring 'popup shell' global user . ': popup-shell' + +# hook -once global ClientCreate .* %{ +# evaluate-commands %sh{ +# if [ "$kak_buflist" != "*debug* *scratch*" ]; then +# echo "delete-buffer *scratch*" +# else +# echo "echo -debug dumping buffers" +# echo "echo -debug %val{buflist}" +# fi +# } +# } + diff --git a/.config/kak/scripts/lsp-diags.py b/.config/kak/scripts/lsp-diags.py new file mode 120000 index 00000000..732dc5e5 --- /dev/null +++ b/.config/kak/scripts/lsp-diags.py @@ -0,0 +1 @@ +/home/fic/dev/kak-lsp-diags/lsp-diags.py \ No newline at end of file diff --git a/.config/kak/scripts/lsp-diags_.py b/.config/kak/scripts/lsp-diags_.py new file mode 100755 index 00000000..dd548ed2 --- /dev/null +++ b/.config/kak/scripts/lsp-diags_.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python + +# pyright: basic, reportUnusedCallResult=false + +import atexit +import sys +import os +import tempfile +import signal + +Position = tuple[int, int] +SpecList = list[tuple[Position, Position]] + +diagnostics: SpecList = [] + + +def parse_specs(data: str): + parsed: SpecList = [] + for entry in data.strip().split(): + if not entry or len(entry) < 9: + continue + range_part, _ = entry.split("|", 1) + start_str, end_str = range_part.split(",") + sl, sc = map(int, start_str.split(".")) + el, ec = map(int, end_str.split(".")) + parsed.append(((sl, sc), (el, ec))) + return parsed + + +def is_cursor_in_any(cursor: Position, diagnostics: SpecList) -> bool: + cl, cc = cursor + for (sl, sc), (el, ec) in diagnostics: + if cl < sl or cl > el: + continue + if sl == el: + if cl == sl and sc <= cc <= ec: + return True + elif cl == sl: + if cc >= sc: + return True + elif cl == el: + if cc <= ec: + return True + elif sl < cl < el: + return True + return False + + +def cleanup(inp: str, outp: str, dir: str): + # subprocess.run(["notify-send", "cleanup runs"]) + try: + os.remove(inp) + os.remove(outp) + os.rmdir(dir) + except FileNotFoundError: + pass + + +def gen_kakoune_output(inp: str, outp: str) -> str: + return f"declare-option -hidden str diagpipe_in {inp}\ndeclare-option -hidden str diagpipe_out {outp}" + + +def daemonize(inp: str, outp: str, dir: str): + # fork and exit parent + if os.fork() > 0: + sys.exit(0) + # new session + os.setsid() + if os.fork() > 0: + # exit first child + sys.exit(0) + + # redirect IO to /dev/null + with open("/dev/null", "rb", 0) as dn: + os.dup2(dn.fileno(), sys.stdin.fileno()) + with open("/dev/null", "ab", 0) as dn: + os.dup2(dn.fileno(), sys.stdout.fileno()) + os.dup2(dn.fileno(), sys.stderr.fileno()) + _ = atexit.register(lambda: cleanup(inp, outp, dir)) + + def on_exit(*_): + # cleanup(inp, outp, dir) + sys.exit(0) + + signal.signal(signal.SIGTERM, on_exit) + signal.signal(signal.SIGINT, on_exit) + + +def main(): + # subprocess.run(["notify-send", "begin loop"]) + # create unique directory and names + fifo_dir = tempfile.mkdtemp(prefix="diagpipe-") + in_path = os.path.join(fifo_dir, "in") + out_path = os.path.join(fifo_dir, "out") + + # create fifos + os.mkfifo(in_path) + os.mkfifo(out_path) + + output = gen_kakoune_output(in_path, out_path) + print(output) + sys.stdout.flush() + daemonize(in_path, out_path, fifo_dir) + + with open(in_path, "r") as infile, open(out_path, "w") as outfile: + diagnostics: SpecList = [] + while True: + line = infile.readline() + if not line: + continue + line = line.strip() + assert isinstance(line, str) + # # subprocess.run(["notify-send", f"Received command: {line}"]) + if line.startswith("set "): + # subprocess.run(["notify-send", f"Received set: {line}"]) + _, payload = line.split(" ", 1) + diagnostics = parse_specs(payload) + _ = outfile.write("ok\n") + outfile.flush() + elif line.startswith("query "): + _, pos = line.split(" ", 1) + l, c = map(int, pos.strip().split()) + result = is_cursor_in_any((l, c), diagnostics) + _ = outfile.write("true\n" if result else "false\n") + outfile.flush() + elif line.startswith("exit"): + # subprocess.run(["notify-send", "exit received"]) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.config/nvchecker/new_ver.json b/.config/nvchecker/new_ver.json index 4998ee2f..7d4ca670 100644 --- a/.config/nvchecker/new_ver.json +++ b/.config/nvchecker/new_ver.json @@ -2,14 +2,14 @@ "version": 2, "data": { "codebook": { - "version": "0.3.0", - "gitref": "refs/tags/v0.3.0", - "url": "https://github.com/blopker/codebook/releases/tag/v0.3.0" + "version": "0.3.2", + "gitref": "refs/tags/v0.3.2", + "url": "https://github.com/blopker/codebook/releases/tag/v0.3.2" }, "iwe": { - "version": "iwe-v0.0.32", - "gitref": "refs/tags/iwe-v0.0.32", - "url": "https://github.com/iwe-org/iwe/releases/tag/iwe-v0.0.32" + "version": "iwe-v0.0.33", + "gitref": "refs/tags/iwe-v0.0.33", + "url": "https://github.com/iwe-org/iwe/releases/tag/iwe-v0.0.33" }, "kak-tree-sitter": { "version": "2.0.0", diff --git a/.config/nvchecker/old_ver.json b/.config/nvchecker/old_ver.json index 4998ee2f..7d4ca670 100644 --- a/.config/nvchecker/old_ver.json +++ b/.config/nvchecker/old_ver.json @@ -2,14 +2,14 @@ "version": 2, "data": { "codebook": { - "version": "0.3.0", - "gitref": "refs/tags/v0.3.0", - "url": "https://github.com/blopker/codebook/releases/tag/v0.3.0" + "version": "0.3.2", + "gitref": "refs/tags/v0.3.2", + "url": "https://github.com/blopker/codebook/releases/tag/v0.3.2" }, "iwe": { - "version": "iwe-v0.0.32", - "gitref": "refs/tags/iwe-v0.0.32", - "url": "https://github.com/iwe-org/iwe/releases/tag/iwe-v0.0.32" + "version": "iwe-v0.0.33", + "gitref": "refs/tags/iwe-v0.0.33", + "url": "https://github.com/iwe-org/iwe/releases/tag/iwe-v0.0.33" }, "kak-tree-sitter": { "version": "2.0.0", diff --git a/.config/nvchecker/old_ver.json~ b/.config/nvchecker/old_ver.json~ index 9b029329..95f09db9 100644 --- a/.config/nvchecker/old_ver.json~ +++ b/.config/nvchecker/old_ver.json~ @@ -7,9 +7,9 @@ "url": "https://github.com/blopker/codebook/releases/tag/v0.3.0" }, "iwe": { - "version": "iwe-v0.0.31", - "gitref": "refs/tags/iwe-v0.0.31", - "url": "https://github.com/iwe-org/iwe/releases/tag/iwe-v0.0.31" + "version": "iwe-v0.0.33", + "gitref": "refs/tags/iwe-v0.0.33", + "url": "https://github.com/iwe-org/iwe/releases/tag/iwe-v0.0.33" }, "kak-tree-sitter": { "version": "2.0.0", diff --git a/.config/qutebrowser/autoconfig.yml b/.config/qutebrowser/autoconfig.yml index da8df7b7..4a216103 100644 --- a/.config/qutebrowser/autoconfig.yml +++ b/.config/qutebrowser/autoconfig.yml @@ -8,3 +8,7 @@ config_version: 2 settings: content.javascript.clipboard: https://github.com: access-paste + statusbar.show: + global: always + tabs.position: + global: top diff --git a/.config/qutebrowser/config.py b/.config/qutebrowser/config.py index 0771ed53..1b337b5d 100644 --- a/.config/qutebrowser/config.py +++ b/.config/qutebrowser/config.py @@ -22,8 +22,6 @@ c.completion.open_categories = [ "filesystem", ] -# c.hints.chars = "tnserigm" -# c.hints.chars = "tnseripldh" c.hints.chars = "tnserigmao" # dark mode @@ -36,14 +34,15 @@ c.colors.webpage.preferred_color_scheme = "dark" # searches c.url.searchengines["DEFAULT"] = "https://www.startpage.com/sp/search?query={}" -c.url.searchengines["!d"] = "https://duckduckgo.com/?q={}" -c.url.searchengines["!aw"] = "https://wiki.archlinux.org/?search={}" -c.url.searchengines["!g"] = ( +c.url.searchengines["d"] = "https://duckduckgo.com/?q={}" +c.url.searchengines["aw"] = "https://wiki.archlinux.org/?search={}" +c.url.searchengines["g"] = ( "http://www.google.com/search?hl=en&source=hp&ie=ISO-8859-l&q={}" ) c.url.searchengines["ap"] = "https://www.archlinux.org/packages/?sort=&q={}" -# with config.pattern("chatgpt.com") as p: -# p.bindings.commands["normal"][""] = "click-element css main" +c.url.searchengines["w"] = ( + "https://en.wikipedia.org/w/index.php?title=Special:Search&search={}" +) config.bind( "", "mode-leave ;; jseval -q document.activeElement.blur()", @@ -58,6 +57,9 @@ config.bind( sets = { "normal": [ + ["tT", "config-cycle tabs.position top left"], + ["sH", "config-cycle statusbar.show always never"], + ["\\", "mode-enter passthrough"], ["m", "scroll left"], ["n", "scroll down"], ["e", "scroll up"], @@ -72,7 +74,12 @@ sets = { ["k", "quickmark-save"], ["J", "search-prev"], ["j", "search-next"], - ["", "hint links spawn --detach mpv {hint-url}"], + [ + ";/", + "hint links spawn --detach mpv --force-window --quiet --keep-open=yes --ytdl {hint-url}", + ], + ["", "spawn --userscript view_in_mpv"], + # ["", "hint links spawn --userscript view_in_mpv"], ["gm", "tab-focus 1"], ["gi", "tab-focus -1"], ["gN", "tab-move +"], @@ -140,6 +147,14 @@ ashen = { "g_12": "#151515", } +c.tabs.padding = {"top": 5, "bottom": 5, "left": 9, "right": 9} + +# c.colors.statusbar.normal.bg = "#00000000" +# c.colors.statusbar.command.bg = "#00000000" +# c.colors.tabs.even.bg = "#00000000" # transparent tabs!! +# c.colors.tabs.odd.bg = "#00000000" +# c.colors.tabs.bar.bg = "#00000000" + # # colors # c.colors.completion.fg = ashen["text"] # c.colors.completion.category.fg = "#F2F2F2" diff --git a/.config/qutebrowser/userscripts/code_select.py b/.config/qutebrowser/userscripts/code_select.py new file mode 100644 index 00000000..8f7fc312 --- /dev/null +++ b/.config/qutebrowser/userscripts/code_select.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +import os +import html +import re +import sys +import xml.etree.ElementTree as ET +try: + import pyperclip +except ImportError: + try: + import pyclip as pyperclip + except ImportError: + PYPERCLIP = False + else: + PYPERCLIP = True +else: + PYPERCLIP = True + + +def parse_text_content(element): + # https://stackoverflow.com/a/35591507/15245191 + magic = ''' + ]>''' + root = ET.fromstring(magic + element) + text = ET.tostring(root, encoding="unicode", method="text") + text = html.unescape(text) + return text + + +def send_command_to_qute(command): + with open(os.environ.get("QUTE_FIFO"), "w") as f: + f.write(command) + + +def main(): + delimiter = sys.argv[1] if len(sys.argv) > 1 else ";" + # For info on qute environment vairables, see + # https://github.com/qutebrowser/qutebrowser/blob/master/doc/userscripts.asciidoc + element = os.environ.get("QUTE_SELECTED_HTML") + code_text = parse_text_content(element) + re_remove_dollars = re.compile(r"^(\$ )", re.MULTILINE) + code_text = re.sub(re_remove_dollars, '', code_text) + if PYPERCLIP: + pyperclip.copy(code_text) + send_command_to_qute( + "message-info 'copied to clipboard: {info}{suffix}'".format( + info=code_text.splitlines()[0].replace("'", "\""), + suffix="..." if len(code_text.splitlines()) > 1 else "" + ) + ) + else: + # Qute's yank command won't copy accross multiple lines so we + # compromise by placing lines on a single line seperated by the + # specified delimiter + code_text = re.sub("(\n)+", delimiter, code_text) + code_text = code_text.replace("'", "\"") + send_command_to_qute("yank inline '{code}'\n".format(code=code_text)) + + +if __name__ == "__main__": + main() diff --git a/.config/qutebrowser/userscripts/getbib b/.config/qutebrowser/userscripts/getbib new file mode 100644 index 00000000..0ab0ba54 --- /dev/null +++ b/.config/qutebrowser/userscripts/getbib @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Qutebrowser userscript scraping the current web page for DOIs and downloading +corresponding bibtex information. + +Set the environment variable 'QUTE_BIB_FILEPATH' to indicate the path to +download to. Otherwise, bibtex information is downloaded to '/tmp' and hence +deleted at reboot. + +Installation: see qute://help/userscripts.html + +Inspired by +https://ocefpaf.github.io/python4oceanographers/blog/2014/05/19/doi2bibtex/ +""" + +import os +import sys +import re +from collections import Counter +from urllib import parse as url_parse +from urllib import request as url_request + + +FIFO_PATH = os.getenv("QUTE_FIFO") + +def message_fifo(message, level="warning"): + """Send message to qutebrowser FIFO. The level must be one of 'info', + 'warning' (default) or 'error'.""" + with open(FIFO_PATH, "w") as fifo: + fifo.write("message-{} '{}'".format(level, message)) + + +source = os.getenv("QUTE_TEXT") +with open(source) as f: + text = f.read() + +# find DOIs on page using regex +dval = re.compile(r'(10\.(\d)+/([^(\s\>\"\<)])+)') +# https://stackoverflow.com/a/10324802/3865876, too strict +# dval = re.compile(r'\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])\S)+)\b') +dois = dval.findall(text) +dois = Counter(e[0] for e in dois) +try: + doi = dois.most_common(1)[0][0] +except IndexError: + message_fifo("No DOIs found on page") + sys.exit() +message_fifo("Found {} DOIs on page, selecting {}".format(len(dois), doi), + level="info") + +# get bibtex data corresponding to DOI +url = "https://dx.doi.org/" + url_parse.quote(doi) +headers = dict(Accept='text/bibliography; style=bibtex') +request = url_request.Request(url, headers=headers) +response = url_request.urlopen(request) +status_code = response.getcode() +if status_code >= 400: + message_fifo("Request returned {}".format(status_code)) + sys.exit() + +# obtain content and format it +bibtex = response.read().decode("utf-8").strip() +bibtex = bibtex.replace(" ", "\n ", 1).\ + replace("}, ", "},\n ").replace("}}", "}\n}") + +# append to file +bib_filepath = os.getenv("QUTE_BIB_FILEPATH", "/tmp/qute.bib") +with open(bib_filepath, "a") as f: + f.write(bibtex + "\n\n") diff --git a/.config/qutebrowser/userscripts/localhost b/.config/qutebrowser/userscripts/localhost new file mode 100644 index 00000000..1715c10a --- /dev/null +++ b/.config/qutebrowser/userscripts/localhost @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [[ $1 -eq 'list' ]] && [[ -z $QUTE_COUNT ]]; +then + PORTS="$(ss -nltp | tail -n +2 | awk '{print $4}' | awk -F: '{print $2}')" + QUTE_COUNT=$(echo "$PORTS" | dmenu ) +fi + +echo open -t localhost:${QUTE_COUNT:-8080} > $QUTE_FIFO diff --git a/.config/qutebrowser/userscripts/qute-bitwarden b/.config/qutebrowser/userscripts/qute-bitwarden new file mode 100644 index 00000000..4e755772 --- /dev/null +++ b/.config/qutebrowser/userscripts/qute-bitwarden @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: Chris Braun (cryzed) +# +# SPDX-License-Identifier: GPL-3.0-or-later + +""" +Insert login information using Bitwarden CLI and a dmenu-compatible application +(e.g. dmenu, rofi -dmenu, ...). +""" + +USAGE = """The domain of the site has to be in the name of the Bitwarden entry, for example: "github.com/cryzed" or +"websites/github.com". The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner: +[USERNAME][PASSWORD], which is compatible with almost all login forms. + +If enabled, with the `--totp` flag, it will also move the TOTP code to the +clipboard, much like the Firefox add-on. + +You must log into Bitwarden CLI using `bw login` prior to use of this script. +The session key will be stored using keyctl for the number of seconds passed to +the --auto-lock option. + +To use in qutebrowser, run: `spawn --userscript qute-bitwarden` +""" + +EPILOG = """Dependencies: tldextract (Python 3 module), pyperclip (optional +Python module, used for TOTP codes), Bitwarden CLI (1.7.4 is known to work +but older versions may well also work) + +WARNING: The login details are viewable as plaintext in qutebrowser's debug log +(qute://log) and might be shared if you decide to submit a crash report!""" + +import argparse +import enum +import functools +import os +import shlex +import subprocess +import sys +import json +import tldextract + +argument_parser = argparse.ArgumentParser( + description=__doc__, + usage=USAGE, + epilog=EPILOG, +) +argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL')) +argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu -i -p Bitwarden', + help='Invocation used to execute a dmenu-provider') +argument_parser.add_argument('--password-prompt-invocation', '-p', default='rofi -dmenu -p "Master Password" -password -lines 0', + help='Invocation used to prompt the user for their Bitwarden password') +argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false', + help="Don't automatically enter insert mode") +argument_parser.add_argument('--totp', '-t', action='store_true', + help="Copy TOTP key to clipboard") +argument_parser.add_argument('--io-encoding', '-i', default='UTF-8', + help='Encoding used to communicate with subprocesses') +argument_parser.add_argument('--merge-candidates', '-m', action='store_true', + help='Merge pass candidates for fully-qualified and registered domain name') +argument_parser.add_argument('--auto-lock', type=int, default=900, + help='Automatically lock the vault after this many seconds') +group = argument_parser.add_mutually_exclusive_group() +group.add_argument('--username-only', '-e', + action='store_true', help='Only insert username') +group.add_argument('--password-only', '-w', + action='store_true', help='Only insert password') +group.add_argument('--totp-only', '-T', + action='store_true', help='Only insert totp code') + +stderr = functools.partial(print, file=sys.stderr) + + +class ExitCodes(enum.IntEnum): + SUCCESS = 0 + FAILURE = 1 + # 1 is automatically used if Python throws an exception + NO_PASS_CANDIDATES = 2 + COULD_NOT_MATCH_USERNAME = 3 + COULD_NOT_MATCH_PASSWORD = 4 + + +def qute_command(command): + with open(os.environ['QUTE_FIFO'], 'w') as fifo: + fifo.write(command + '\n') + fifo.flush() + + +def ask_password(password_prompt_invocation): + process = subprocess.run( + shlex.split(password_prompt_invocation), + text=True, + stdout=subprocess.PIPE, + ) + if process.returncode > 0: + raise Exception('Could not unlock vault') + master_pass = process.stdout.strip() + return subprocess.check_output( + ['bw', 'unlock', '--raw', '--passwordenv', 'BW_MASTERPASS'], + env={**os.environ, 'BW_MASTERPASS': master_pass}, + text=True, + ).strip() + + +def get_session_key(auto_lock, password_prompt_invocation): + if auto_lock == 0: + subprocess.call(['keyctl', 'purge', 'user', 'bw_session']) + return ask_password(password_prompt_invocation) + else: + process = subprocess.run( + ['keyctl', 'request', 'user', 'bw_session'], + text=True, + stdout=subprocess.PIPE, + ) + key_id = process.stdout.strip() + if process.returncode > 0: + session = ask_password(password_prompt_invocation) + if not session: + raise Exception('Could not unlock vault') + key_id = subprocess.check_output( + ['keyctl', 'add', 'user', 'bw_session', session, '@u'], + text=True, + ).strip() + + if auto_lock > 0: + subprocess.call(['keyctl', 'timeout', str(key_id), str(auto_lock)]) + return subprocess.check_output( + ['keyctl', 'pipe', str(key_id)], + text=True, + ).strip() + + +def pass_(domain, encoding, auto_lock, password_prompt_invocation): + session_key = get_session_key(auto_lock, password_prompt_invocation) + process = subprocess.run( + ['bw', 'list', 'items', '--nointeraction', '--session', session_key, '--url', domain], + capture_output=True, + ) + + err = process.stderr.decode(encoding).strip() + if err: + msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain, err) + stderr(msg) + + if "Vault is locked" in err: + stderr("Bitwarden Vault got locked, trying again with clean session") + return pass_(domain, encoding, 0, password_prompt_invocation) + + if process.returncode: + return '[]' + + out = process.stdout.decode(encoding).strip() + + return out + + +def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_prompt_invocation): + session_key = get_session_key(auto_lock, password_prompt_invocation) + process = subprocess.run( + ['bw', 'get', 'totp', '--nointeraction', '--session', session_key, selection_id], + capture_output=True, + ) + + err = process.stderr.decode(encoding).strip() + if err: + # domain_name instead of selection_id to make it more user-friendly + msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain_name, err) + stderr(msg) + + if "Vault is locked" in err: + stderr("Bitwarden Vault got locked, trying again with clean session") + return get_totp_code(selection_id, domain_name, encoding, 0, password_prompt_invocation) + + if process.returncode: + return '[]' + + out = process.stdout.decode(encoding).strip() + + return out + + +def dmenu(items, invocation, encoding): + command = shlex.split(invocation) + process = subprocess.run(command, input='\n'.join( + items).encode(encoding), stdout=subprocess.PIPE) + return process.stdout.decode(encoding).strip() + + +def fake_key_raw(text): + for character in text: + # Escape all characters by default, space requires special handling + sequence = '" "' if character == ' ' else r'\{}'.format(character) + qute_command('fake-key {}'.format(sequence)) + + +def main(arguments): + if not arguments.url: + argument_parser.print_help() + return ExitCodes.FAILURE + + extract_result = tldextract.extract(arguments.url) + + # Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains), + # the registered domain name and finally: the IPv4 address if that's what + # the URL represents + candidates = [] + for target in filter( + None, + [ + extract_result.fqdn, + ( + extract_result.top_domain_under_public_suffix + if hasattr(extract_result, "top_domain_under_public_suffix") + else extract_result.registered_domain + ), + extract_result.subdomain + "." + extract_result.domain, + extract_result.domain, + extract_result.ipv4, + ], + ): + target_candidates = json.loads( + pass_( + target, + arguments.io_encoding, + arguments.auto_lock, + arguments.password_prompt_invocation, + ) + ) + if not target_candidates: + continue + + candidates = candidates + target_candidates + if not arguments.merge_candidates: + break + else: + if not candidates: + stderr('No pass candidates for URL {!r} found!'.format( + arguments.url)) + return ExitCodes.NO_PASS_CANDIDATES + + if len(candidates) == 1: + selection = candidates.pop() + else: + choices = ['{:s} | {:s}'.format(c['name'], c['login']['username']) for c in candidates] + choice = dmenu(choices, arguments.dmenu_invocation, arguments.io_encoding) + choice_tokens = choice.split('|') + choice_name = choice_tokens[0].strip() + choice_username = choice_tokens[1].strip() + selection = next((c for (i, c) in enumerate(candidates) + if c['name'] == choice_name + and c['login']['username'] == choice_username), + None) + + # Nothing was selected, simply return + if not selection: + return ExitCodes.SUCCESS + + username = selection['login']['username'] + password = selection['login']['password'] + totp = selection['login']['totp'] + + if arguments.username_only: + fake_key_raw(username) + elif arguments.password_only: + fake_key_raw(password) + elif arguments.totp_only: + # No point in moving it to the clipboard in this case + fake_key_raw( + get_totp_code( + selection['id'], + selection['name'], + arguments.io_encoding, + arguments.auto_lock, + arguments.password_prompt_invocation, + ) + ) + else: + # Enter username and password using fake-key and (which seems to work almost universally), then switch + # back into insert-mode, so the form can be directly submitted by + # hitting enter afterwards + fake_key_raw(username) + qute_command('fake-key ') + fake_key_raw(password) + + if arguments.insert_mode: + qute_command('mode-enter insert') + + # If it finds a TOTP code, it copies it to the clipboard, + # which is the same behavior as the Firefox add-on. + if not arguments.totp_only and totp and arguments.totp: + # The import is done here, to make pyperclip an optional dependency + import pyperclip + pyperclip.copy( + get_totp_code( + selection['id'], + selection['name'], + arguments.io_encoding, + arguments.auto_lock, + arguments.password_prompt_invocation, + ) + ) + + return ExitCodes.SUCCESS + + +if __name__ == '__main__': + arguments = argument_parser.parse_args() + sys.exit(main(arguments)) diff --git a/.config/qutebrowser/userscripts/readability b/.config/qutebrowser/userscripts/readability new file mode 100644 index 00000000..07095a5b --- /dev/null +++ b/.config/qutebrowser/userscripts/readability @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# Executes python-readability on current page and opens the summary as new tab. +# +# Depends on the python-readability package, or its fork: +# +# - https://github.com/buriy/python-readability +# - https://github.com/bookieio/breadability +# +# Usage: +# :spawn --userscript readability +# +import codecs, os + +tmpfile = os.path.join( + os.environ.get('QUTE_DATA_DIR', + os.path.expanduser('~/.local/share/qutebrowser')), + 'userscripts/readability.html') + +if not os.path.exists(os.path.dirname(tmpfile)): + os.makedirs(os.path.dirname(tmpfile)) + +# Styling for dynamic window margin scaling and line height +HEADER = """ + + + + + %s + + + +""" + +with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source: + data = source.read() + + try: + from breadability.readable import Article as reader + doc = reader(data, os.environ['QUTE_URL']) + title = doc._original_document.title + content = HEADER % title + doc.readable + "" + except ImportError: + from readability import Document + doc = Document(data) + title = doc.title() + content = doc.summary().replace('', HEADER % title) + + # add a class to make styling the page easier + content = content.replace('', '') + + with codecs.open(tmpfile, 'w', 'utf-8') as target: + target.write(content.lstrip()) + + with open(os.environ['QUTE_FIFO'], 'w') as fifo: + fifo.write('open -t %s' % tmpfile) diff --git a/.config/qutebrowser/userscripts/view_in_mpv b/.config/qutebrowser/userscripts/view_in_mpv new file mode 100644 index 00000000..4f371c6b --- /dev/null +++ b/.config/qutebrowser/userscripts/view_in_mpv @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# +# Behavior: +# Userscript for qutebrowser which views the current web page in mpv using +# sensible mpv-flags. While viewing the page in MPV, all