diff options
28 files changed, 1145 insertions, 115 deletions
@@ -71,7 +71,6 @@ export GOROOT="/usr/lib/go" export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle" export HISTFILE="$XDG_STATE_HOME/bash/history" export INPUTRC="$XDG_CONFIG_HOME/readline/inputrc" -export _JAVA_OPTIONS="-Djava.util.prefs.userRoot=\"$XDG_CONFIG_HOME/java\" -Djavafx.cachedir=\"$XDG_CACHE_HOME/openjfx\"" export MAILDIR="$HOME/mail" export NODE_REPL_HISTORY="$XDG_DATA_HOME/node_repl_history" export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc" @@ -82,10 +81,12 @@ export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/startup.py" export PYTHONUSERBASE="$XDG_DATA_HOME/python" export RLWRAP_HOME="$XDG_CACHE_HOME/rlwrap" export RUSTUP_HOME="$XDG_DATA_HOME/rustup" +export SQLITE_HISTORY="$XDG_STATE_HOME/sqlite_history" export W3M_DIR="$XDG_STATE_HOME/w3m" export WGETRC="$XDG_CONFIG_HOME/wgetrc" export XCOMPOSECACHE="$XDG_CACHE_HOME/X11/xcompose" export XCOMPOSEFILE="$XDG_CONFIG_HOME/X11/xcompose" +export _JAVA_OPTIONS="-Djava.util.prefs.userRoot=\"$XDG_CONFIG_HOME/java\" -Djavafx.cachedir=\"$XDG_CACHE_HOME/openjfx\"" export REPODIR="$HOME/code" export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" @@ -116,6 +117,7 @@ alias z='2>/dev/null zathura --fork "$@" 2>/dev/null' PS1='\[\e[96;1m\]\u \[\e[39m\]\W \[\e[96m\]〉\[\e[0m\]' eval "$(fzf --bash)" +eval "$(eww shell-completions --shell bash)" # Autocompletions for auto-cpufreq. Inlined from the generated output # for performance reasons. diff --git a/.config/X11/xcompose b/.config/X11/xcompose index 4346e5a..8141415 100644 --- a/.config/X11/xcompose +++ b/.config/X11/xcompose @@ -54,6 +54,8 @@ <Multi_key> <x> <x> : "×" # Typographical Symbols +<Multi_key> <greater> <period> : "›" +<Multi_key> <less> <period> : "‹" <Multi_key> <greater> <greater> : "»" <Multi_key> <less> <less> : "«" <Multi_key> <less> <minus> : "←" @@ -491,4 +493,5 @@ <Multi_key> <underscore> <t> : "ₜ" <Multi_key> <underscore> <u> : "ᵤ" <Multi_key> <underscore> <v> : "ᵥ" -<Multi_key> <underscore> <x> : "ₓ"
\ No newline at end of file +<Multi_key> <underscore> <x> : "ₓ" + diff --git a/.config/emacs/early-init.el b/.config/emacs/early-init.el index b48d643..0dfbfa6 100644 --- a/.config/emacs/early-init.el +++ b/.config/emacs/early-init.el @@ -76,17 +76,14 @@ ;; set threshold to 8 MiB which seems to be a good middleground for now. ;; A higher threshold means less garbage collections but I’ve had issues ;; with those garbage collections causing long freezes when they occur. -(let ((saved-file-name-handler-alist file-name-handler-alist) - (saved-vc-handled-backends vc-handled-backends)) - (setopt file-name-handler-alist nil - vc-handled-backends nil) +(let ((saved-file-name-handler-alist file-name-handler-alist)) + (setopt file-name-handler-alist nil) (add-hook 'emacs-startup-hook (defun mm-restore-emacs-settings () (setopt gc-cons-threshold (* 1024 1024 8) gc-cons-percentage 0.1 - file-name-handler-alist saved-file-name-handler-alist - vc-handled-backends saved-vc-handled-backends)))) + file-name-handler-alist saved-file-name-handler-alist)))) ;;; Avoid Flashbang diff --git a/.config/emacs/init.el b/.config/emacs/init.el index a8620d6..eb0f23e 100644 --- a/.config/emacs/init.el +++ b/.config/emacs/init.el @@ -8,7 +8,7 @@ ;; Lord knows why this needs to be so complicated… ;; ;; The ‘eval’ is required in the case that this file is byte-compiled. -(if mm-darwin-p +(if mm-humanwave-p (eval '(setq inhibit-startup-echo-area-message "thomasvoss")) (eval '(setq inhibit-startup-echo-area-message "thomas"))) @@ -177,17 +177,17 @@ buffer suppressed." (echo-keystrokes 0.01) ; 0 disables echoing (echo-keystrokes-help nil) (extended-command-suggest-shorter nil) - (initial-buffer-choice t) + (initial-buffer-choice nil) (initial-scratch-message mm-initial-scratch-message) (kill-do-not-save-duplicates t) (large-file-warning-threshold nil) (make-backup-files nil) - (mode-require-final-newline nil) + (mode-require-final-newline t) (next-error-recenter '(4)) ; ‘center of window’ (read-extended-command-predicate #'command-completion-default-include-p) (remote-file-name-inhibit-auto-save t) (remote-file-name-inhibit-delete-by-moving-to-trash t) - (require-final-newline nil) + (require-final-newline mm-humanwave-p) (save-interprogram-paste-before-kill t) (user-full-name "Thomas Voss") (user-mail-address "mail@thomasvoss.com") @@ -294,15 +294,6 @@ the buffer without saving it." (advice-add command :around #'mm-auto-create-directories)) -;;; Emacs Lisp Libraries - -;; The following are included here because they’re required by some of -;; the modules. - -(use-package async - :ensure t) - - ;;; Load Modules (require 'mm-abbrev) ; Text Expansion @@ -326,6 +317,28 @@ the buffer without saving it." (require 'mm-darwin)) ; MacOS (when mm-lsp-p (require 'mm-lsp)) ; Language Server Protocol +(when mm-humanwave-p + (require 'mm-humanwave)) ; Humanwave Extras + +;; TODO: Put this somewhere reasonable + +(defvar mm-placeholder-text "‹XX›") + +(defun mm-placeholder-insert () + (interactive) + (insert mm-placeholder-text)) + +(defun mm-placeholder-next () + (interactive) + (let ((point (point))) + (if (search-forward mm-placeholder-text nil :noerror) + (delete-region (match-beginning 0) (match-end 0)) + (goto-char point) + (message "No more placeholders after point.")))) + +(keymap-global-set "C-c i p" #'mm-placeholder-insert) +(keymap-global-set "C-c n" #'mm-placeholder-next) + ;;; Postamble @@ -333,4 +346,4 @@ the buffer without saving it." (add-hook 'after-init-hook (defun mm-echo-init-time () (message (emacs-init-time "Emacs initialized in %.2f seconds"))) - 100)
\ No newline at end of file + 100) diff --git a/.config/emacs/modules/mm-abbrev.el b/.config/emacs/modules/mm-abbrev.el index 937d558..66ca8d7 100644 --- a/.config/emacs/modules/mm-abbrev.el +++ b/.config/emacs/modules/mm-abbrev.el @@ -78,6 +78,14 @@ case-sensitive to avoid unexpected abbreviation expansions." "sme" "save-mark-and-excursion" "sr" "save-restriction") +(when mm-humanwave-p + (with-eval-after-load 'python-ts-mode + (mm-define-abbreviations python-ts-mode-abbrev-table + "empb" "with emphasize.Block():" + "empf" "@emphasize.func" + "empi" "from shared.system import emphasize" + "empt" "emphasize.this"))) + ;;; Template Configuration diff --git a/.config/emacs/modules/mm-editing.el b/.config/emacs/modules/mm-editing.el index 14f5211..6054f5a 100644 --- a/.config/emacs/modules/mm-editing.el +++ b/.config/emacs/modules/mm-editing.el @@ -56,6 +56,7 @@ (go-ts-mode . (:extras go-ts-mode-indent-offset)) (gsp-ts-mode . (:width 2 :extras gsp-ts-mode-indent-rules)) (helpful-mode . (:width 8)) ; GNU code uses 8-column tabs + (json-ts-mode . (:extras json-ts-mode-indent-offset)) (latex-mode . (:width 2)) (lisp-data-mode . (:spaces t)) (lisp-interaction-mode . (:spaces t)) @@ -66,8 +67,12 @@ (python-ts-mode . (:extras python-indent-offset)) (sgml-mode . (:extras sgml-basic-offset)) (sh-mode . (:extras sh-basic-offset)) + (sql-mode . (:extras sqlind-basic-offset)) (tex-mode . (:width 2)) - (vimscript-ts-mode . (:extras vimscript-ts-mode-indent-level))) + (typescript-ts-mode . (:extras typescript-ts-mode-indent-offset)) + (vimscript-ts-mode . (:extras vimscript-ts-mode-indent-level)) + (vue-ts-mode . (:extras (typescript-ts-mode-indent-offset + vue-ts-mode-indent-offset)))) "Alist of indentation settings. Each pair in this alist is of the form (MODE . SETTINGS) where MODE specifies the mode for which the given SETTINGS should apply. @@ -122,6 +127,11 @@ those should be listed in `mm-editing-indentation-settings'." extras)) (set (make-local-variable extra) tabsize))))) +(use-package sh-mode + :custom + (sh-indent-for-case-label 0) + (sh-indent-for-case-alt #'+)) + ;;; Code Commenting @@ -295,6 +305,18 @@ surround with spaces." (push '("`" . "'") surround-pairs)))) +;;; Insert Webpage Contents + +(defun mm-insert-from-url (url) + "Insert the contents of URL at point." + (interactive + (let ((url-at-point (thing-at-point 'url))) + (list (read-string + (format-prompt "URL" url-at-point) + nil nil url-at-point)))) + (call-process "curl" nil '(t nil) nil url)) + + ;;; Emmet Mode (defun mm-editing-emmet-dwim (arg) @@ -315,6 +337,36 @@ is as described by `emmet-expand-line'." (emmet-self-closing-tag-style "")) +;;; JQ Manipulation in JSON Mode + +(defun mm-jq-filter (query &optional beg end) + "TODO" + (interactive + (list + (read-string (format-prompt "Query" nil)) + (when (use-region-p) (region-beginning)) + (when (use-region-p) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (temp-buffer (generate-new-buffer "* jq temp*")) + (exit-code (call-process-region beg end "jq" nil temp-buffer nil + "--tab" query)) + (output (with-current-buffer temp-buffer (buffer-string)))) + (if (zerop exit-code) + (atomic-change-group + (delete-region beg end) + (insert output) + (indent-region beg (point))) + (message "%s" output)) + (kill-buffer temp-buffer))) + +(use-package json-ts-mode + :bind ( :map json-ts-mode-map + ("C-|" . #'mm-jq-filter)) + :config + (require 'live-jq)) + + ;;; Number Formatting (use-package number-format-mode @@ -326,7 +378,9 @@ is as described by `emmet-expand-line'." ;;; Additional Major Modes (use-package awk-ts-mode :ensure t) +(use-package cmake-mode :ensure t) (use-package git-modes :ensure t) +(use-package po-mode :ensure t) (use-package sed-mode :ensure t) (use-package csv-mode @@ -358,6 +412,12 @@ is as described by `emmet-expand-line'." ;;; Add Missing Extensions (dolist (pattern '("\\.tmac\\'" "\\.mom\\'")) - (add-to-list 'auto-mode-alist (cons pattern 'nroff-mode))) + (add-to-list 'auto-mode-alist (cons pattern #'nroff-mode))) + + +;;; Subword Navigation + +(use-package subword + :hook prog-mode) -(provide 'mm-editing)
\ No newline at end of file +(provide 'mm-editing) diff --git a/.config/emacs/modules/mm-humanwave.el b/.config/emacs/modules/mm-humanwave.el new file mode 100644 index 0000000..3aa97b3 --- /dev/null +++ b/.config/emacs/modules/mm-humanwave.el @@ -0,0 +1,141 @@ +;;; mm-humanwave.el --- Humanwave extras -*- lexical-binding: t; -*- + + +;;; Query the Backend + +(defvar mm--humanwave-query-history nil + "History for endpoints given to `mm-humanwave-query'.") + +(defun mm-humanwave-query (endpoint &optional method) + "Query and display the result of an HTTP reqiest on ENDPOINT. +If METHOD is nil, a GET request is performed." + (interactive + (let* ((query (read-string (format-prompt "Query" nil) + (car-safe mm--humanwave-query-history) + 'mm--humanwave-query-history)) + (parts (string-split (string-trim query) " " :omit-nulls))) + (when (length> parts 2) + (user-error "Queries must be of the form `METHOD ENDPOINT' or `ENDPOINT'.")) + (nreverse parts))) + (let* ((project-root (project-root (project-current :maybe-prompt))) + (qry-path (expand-file-name "qry" project-root)) + extras) + (unless (file-executable-p qry-path) + (user-error "No `qry' executable found in the project root")) + (let ((output-buffer (get-buffer-create "*Query Response*"))) + (with-current-buffer output-buffer + (delete-region (point-min) (point-max)) + (call-process qry-path nil t nil + (string-trim endpoint) "-X" (or method "GET")) + (unless (eq major-mode 'json-ts-mode) + (json-ts-mode)) + (goto-char (point-min))) + (display-buffer output-buffer)))) + +(keymap-set project-prefix-map "q" #'mm-humanwave-query) + + +;;; IMenu Support for Handlers + +(defvar mm-humanwave-handler--regexp + (rx bol + (* blank) + (or "if" "elif") + (* blank) + (or "topic" "schedule") + (* blank) + "==" + (* blank) + (or (seq ?\' (* (not ?\')) ?\') + (seq ?\" (* (not ?\")) ?\")) + (* blank) + ?: + (* blank) + eol)) + +(defun mm-humanwave-handler-topic-imenu-index () + (let ((tree-index (when (fboundp 'treesit-simple-imenu--generic-function) + (treesit-simple-imenu--generic-function + treesit-simple-imenu-settings))) + (topic-index '())) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward mm-humanwave-handler--regexp nil :noerror) + (let ((label (match-string 0)) + (pos (match-beginning 0))) + (push (cons (format "Topic: %s" (string-trim label)) pos) topic-index)))) + (append (nreverse topic-index) tree-index))) + +(defun mm-humanwave-handler-topic-imenu-setup () + "Setup custom imenu index for `python-ts-mode'." + (when (and (string-match-p "handlers?" (or (buffer-file-name) "")) + (derived-mode-p #'python-ts-mode)) + (setq-local imenu-create-index-function + #'mm-humanwave-handler-topic-imenu-index))) + +(add-hook 'after-change-major-mode-hook + #'mm-humanwave-handler-topic-imenu-setup) + + +;;; Insert Imports in Vue + +(defun mm-humanwave-insert-vue-import-path (base-directory target-file) + "Insert an import directive at POINT. +The import directive imports TARGET-FILE relative from BASE-DIRECTORY. +When called interactively BASE-DIRECTORY is the directory of the current +open Vue file and TARGET-FILE is a file in the current project that is +queried interactively. + +When called interactively the prefix argument can be used to emulate the +behaviour of the INCLUDE-ALL-P argument to `mm-project-read-file-name'." + (interactive + (list + default-directory + (mm-humanwave-project-read-file-name current-prefix-arg))) + (let ((path (file-name-sans-extension + (file-relative-name target-file base-directory)))) + (unless (string-match-p "/" path) + (setq path (concat "./" path))) + (insert "import ") + (save-excursion + (insert (thread-last + (file-name-base path) + (mm-string-split "-") + (mapconcat #'capitalize))) + (push-mark (point)) + (insert (format " from '%s';" path))))) + +(defun mm-humanwave-project-read-file-name (&optional include-all-p) + "Prompt for a project file. +This function is similar to `project-find-file', but it returns the path +to the selected file instead of opening it. + +When called interactively the selected file is printed to the minibuffer, +otherwise it is returned. + +The prefix argument INCLUDE-ALL-P is the same as the INCLUDE-ALL argument +to the `project-find-file' command." + (interactive "P") + (let* ((project (project-current :maybe-prompt)) + (root (project-root project)) + (files (if include-all-p + (let ((vc-ignores (mapcar + (lambda (dir) (concat dir "/")) + vc-directory-exclusion-list))) + (project--files-in-directory root vc-ignores)) + (project-files project))) + (canditates (mapcar (lambda (f) (file-relative-name f root)) + files)) + (table (lambda (string predicate action) + (if (eq action 'metadata) + '(metadata (category . file)) + (complete-with-action action canditates string predicate)))) + (default-directory root) + (choice (completing-read (format-prompt "Find project file" nil) + table nil :require-match))) + (let ((path (expand-file-name choice root))) + (if (called-interactively-p 'any) + (message "%s" path) + path)))) + +(provide 'mm-humanwave) diff --git a/.config/emacs/modules/mm-modeline.el b/.config/emacs/modules/mm-modeline.el index d1ec6f0..44fc115 100644 --- a/.config/emacs/modules/mm-modeline.el +++ b/.config/emacs/modules/mm-modeline.el @@ -90,7 +90,7 @@ the text it should be mapped to.") ((derived-mode-p 'comint-mode) "$ ") ((derived-mode-p 'conf-mode) "# ") ((derived-mode-p 'prog-mode) "λ ") - ((derived-mode-p 'special-mode) "❇ ") + ((derived-mode-p 'special-mode) "★ ") ((derived-mode-p 'text-mode) "§ ") (:default "")) 'face 'mm-modeline-major-mode-symbol-face)) diff --git a/.config/emacs/modules/mm-projects.el b/.config/emacs/modules/mm-projects.el index 00342f5..02d7af4 100644 --- a/.config/emacs/modules/mm-projects.el +++ b/.config/emacs/modules/mm-projects.el @@ -19,7 +19,7 @@ This is intended to be called interactively via (project-find-file "Find File" ?f) (mm-projects-project-magit-status "Git Status" ?s))) :config - (unless mm-darwin-p + (unless mm-humanwave-p (if-let ((repo-directory (getenv "REPODIR"))) (let* ((list-dir (lambda (path) @@ -47,9 +47,10 @@ This is intended to be called interactively via (use-package magit :ensure t - :bind ( :map magit-status-mode-map - ("[" . magit-section-backward-sibling) - ("]" . magit-section-forward-sibling)) + :bind (("C-c b" . magit-blame-addition) + :map magit-status-mode-map + ("[" . magit-section-backward-sibling) + ("]" . magit-section-forward-sibling)) :custom (git-commit-style-convention-checks '(non-empty-second-line overlong-summary-line)) @@ -70,11 +71,18 @@ This is intended to be called interactively via "push" "-v" args remote (format "refs/heads/%s:refs/heads/%s" branch branch))))) (transient-append-suffix #'magit-push '(1 -1) - '("a" "all remotes" mm-projects-magit-push-current-to-all-remotes))) + '("a" "all remotes" mm-projects-magit-push-current-to-all-remotes)) + (add-to-list 'magit-blame-styles + '(margin + (show-lines . t) + (margin-format . (" %C %a" " %s%f" " ")) + (margin-width . 73) + (margin-face . magit-blame-margin) + (margin-body-face . (magit-blame-dimmed))))) (use-package magit-repos :ensure nil ; Part of ‘magit’ - :if (not mm-darwin-p) + :if (not mm-humanwave-p) :commands (magit-list-repositories) :init (if-let ((directory (getenv "REPODIR"))) @@ -96,4 +104,11 @@ This is intended to be called interactively via (require 'ansi-color) (add-hook 'compilation-filter-hook #'ansi-color-compilation-filter)) -(provide 'mm-projects)
\ No newline at end of file + +;;; GitHub Pull Requests + +(require 'gh) +(keymap-global-set "C-c p c" #'gh-create-pr) +(keymap-global-set "C-c p o" #'gh-open-previous-pr) + +(provide 'mm-projects) diff --git a/.config/emacs/modules/mm-theme.el b/.config/emacs/modules/mm-theme.el index 95c96c9..cde214a 100644 --- a/.config/emacs/modules/mm-theme.el +++ b/.config/emacs/modules/mm-theme.el @@ -8,7 +8,7 @@ ;;; Fonts -(defvar mm-theme-monospace-font `(,(if mm-darwin-p +(defvar mm-theme-monospace-font `(,(if mm-humanwave-p "Iosevka Custom" "Iosevka Smooth") :weight regular @@ -16,7 +16,9 @@ "The default monospace font. This is a plist containing a font name, -weight, and -height.") -(defvar mm-theme-proportional-font '("OpenSans" :weight regular :height 162) +(defvar mm-theme-proportional-font + `(,(if mm-darwin-p "Microsoft Sans Serif" "OpenSans") + :weight regular :height 162) "The default proportional font. This is a plist containing a font name, -weight, and -height.") @@ -164,6 +166,16 @@ See also the `mm-theme-background-opacity' variable." (("C-c h l" . pulsar-highlight-dwim))) +;;; In-buffer highlighting + +(require 'hi-lock) ; For extra face definitions +(use-package highlighter + :bind (("C-c h m" . #'highlighter-mark) + ("C-c h u" . #'highlighter-unmark) + ("C-c h U" . #'highlighter-unmark-buffer)) + :commands (highlighter-mark highlighter-unmark highlighter-unmark-buffer)) + + ;;; Add Padding (use-package spacious-padding @@ -222,7 +234,7 @@ See also the `mm-theme-background-opacity' variable." ;;; Indent Guides -(when mm-darwin-p +(when mm-humanwave-p (use-package highlight-indent-guides :ensure t :hook ((jinja2-mode vue-ts-mode mhtml-mode) . highlight-indent-guides-mode) @@ -231,4 +243,4 @@ See also the `mm-theme-background-opacity' variable." (highlight-indent-guides-auto-even-face-perc 30) (highlight-indent-guides-auto-odd-face-perc 0))) -(provide 'mm-theme)
\ No newline at end of file +(provide 'mm-theme) diff --git a/.config/emacs/modules/mm-treesit.el b/.config/emacs/modules/mm-treesit.el index 4e052de..bd146ce 100644 --- a/.config/emacs/modules/mm-treesit.el +++ b/.config/emacs/modules/mm-treesit.el @@ -35,6 +35,8 @@ "https://github.com/tree-sitter/tree-sitter-java") (javascript "https://github.com/tree-sitter/tree-sitter-javascript") + (json + "https://github.com/tree-sitter/tree-sitter-json") (markdown "https://github.com/tree-sitter-grammars/tree-sitter-markdown" "split_parser" "tree-sitter-markdown/src") @@ -43,6 +45,9 @@ "split_parser" "tree-sitter-markdown-inline/src") (python "https://github.com/tree-sitter/tree-sitter-python") + (tsx + "https://github.com/tree-sitter/tree-sitter-typescript" + "master" "tsx/src") (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src") @@ -112,11 +117,13 @@ The parsers are taken from `treesit-language-source-alist'." ;; anyway. Same goes for ‘typescript-ts-mode’. (defvar mm-treesit-language-file-name-alist '((go . "\\.go\\'") - (go-mod . "/go\\.mod\\'") + (gomod . "/go\\.mod\\'") + (json . "\\.json\\'") + (tsx . "\\.tsx\\'") (typescript . "\\.ts\\'")) "Alist mapping languages to their associated file-names. This alist is a set of pairs of the form (LANG . REGEXP) where LANG is -the symbol corresponding to a major mode with the ‘-ts-mode’ suffix +the symbol corresponding to a major mode with the `-ts-mode' suffix removed. REGEXP is a regular expression matching filenames for which the associated language’s major-mode should be enabled. @@ -132,9 +139,9 @@ languages should be listed here.") (dolist (spec treesit-language-source-alist) (let* ((lang (car spec)) - (lang (alist-get lang mm-treesit-language-remap-alist lang)) - (name-mode (intern (format "%s-mode" lang))) - (name-ts-mode (intern (format "%s-ts-mode" lang)))) + (lang-remap (alist-get lang mm-treesit-language-remap-alist lang)) + (name-mode (intern (format "%s-mode" lang-remap))) + (name-ts-mode (intern (format "%s-ts-mode" lang-remap)))) ;; If ‘name-ts-mode’ is already in ‘auto-mode-alist’ then we don’t ;; need to do anything, however if that’s not the case then if ;; ‘name-ts-mode’ and ‘name-mode’ are both bound we do a simple @@ -155,6 +162,9 @@ languages should be listed here.") (add-to-list 'auto-mode-alist (cons file-regexp name-ts-mode)) (warn "Unable to determine the extension for `%s'." name-ts-mode)))))) +;; JavaScript being difficult as usual +(add-to-list 'major-mode-remap-alist '(javascript-mode . js-ts-mode)) + ;;; Hack For C23 @@ -212,4 +222,4 @@ back to regular `expreg-expand'." :commands (mm-expreg-expand mm-expreg-expand-dwim) :bind ("M-SPC" . mm-expreg-expand-dwim)) -(provide 'mm-treesit)
\ No newline at end of file +(provide 'mm-treesit) diff --git a/.config/emacs/site-lisp/gh.el b/.config/emacs/site-lisp/gh.el new file mode 100644 index 0000000..23086e5 --- /dev/null +++ b/.config/emacs/site-lisp/gh.el @@ -0,0 +1,59 @@ +;;; gh.el --- GitHub integration for Emacs -*- lexical-binding: t; -*- + +(defun gh-get-labels () + "Return a list of labels in the current GitHub repository." + (with-temp-buffer + (call-process "gh" nil t nil "label" "list" "--sort" "name" "--json" "name") + (goto-char (point-min)) + (seq-map (lambda (x) (gethash "name" x)) + (json-parse-buffer)))) + +;; TODO: Set title and body in a buffer like Magit +(defun gh-create-pr (title &optional labels draftp) + "Create a GitHub pull request. +If DRAFTP is non-nil, the PR will be created as a draft. + +LABELS is a list of labels. A list of available labels can be fetched +via `gh-get-labels'." + (interactive + (list + (read-string (format-prompt "PR Title" nil)) + (completing-read-multiple (format-prompt "PR Labels" nil) + (gh-get-labels)) + (y-or-n-p "Create PR as a draft? "))) + (let* ((project (project-name (project-current))) + (flags `("--fill-verbose" "--assignee" "@me")) + (label-string (mapconcat #'identity labels ","))) + ;; TODO: Remove this + (when (string= project "blixem") + (setq title (format "%s %s" (car (vc-git-branches)) title))) + (setq flags (append flags `("--title" ,title))) + (when draftp + (setq flags (append flags '("--draft")))) + (when labels + (setq flags (append flags `("--label" ,label-string)))) + (with-temp-buffer + (apply #'call-process "gh" nil t nil "pr" "create" flags) + (message (buffer-string))))) + +(defvar gh-pr-regexp + "\\`https://\\(?:www\\.\\)?github\\.com/[^/]+/[^/]+/pull/[[:digit:]]+\\'") + +(defun gh--pr-link-p (s) + (declare (pure t) (side-effect-free t)) + (string-match-p gh-pr-regexp s)) + +(defun gh-open-previous-pr () + "Open the previous GitHub pull request. +Opens the previous pull request created by `gh-create-pr' by searching +for the echoed URL in the `*Messages*' buffer." + (interactive) + (with-current-buffer "*Messages*" + (goto-char (point-max)) + (while (not (gh--pr-link-p (buffer-substring-no-properties + (pos-bol) (pos-eol)))) + (unless (line-move -1 :noerror) + (user-error "No previous pull request found."))) + (browse-url-at-point))) + +(provide 'gh) diff --git a/.config/emacs/site-lisp/highlighter.el b/.config/emacs/site-lisp/highlighter.el new file mode 100644 index 0000000..ce67ac8 --- /dev/null +++ b/.config/emacs/site-lisp/highlighter.el @@ -0,0 +1,128 @@ +;;; highlighter.el --- In-buffer highlighting commands -*- lexical-binding: t; -*- + +(require 'seq) + +(defgroup highlighter nil + "Customization group for `highlighter'." + :group 'convenience) + +(defcustom highlighter-default-face 'match + "The default face used by `highlighter-mark'." + :type 'face + :package-version '(highlighter . "1.0.0") + :group 'highlighter) + +(defun highlighter-mark (arg) + "Highlight text in the buffer. +Highlight the current line or region if it is active. Text is +highlighted using the face specified by `highlighter-default-face'. + +With ARG, interactively pick a face to highlight with." + (declare (interactive-only t)) + (interactive "P") + (let ((bounds (if (use-region-p) + (region-bounds) + `((,(pos-bol) . ,(pos-eol))))) + (face (when arg + (highlighter--read-face-name "Highlight with face" #'facep)))) + (highlighter-mark-region bounds face)) + (when (region-active-p) + (deactivate-mark))) + +(defun highlighter-unmark (arg) + "Remove highlights in the buffer. + +Remove highlights from the current line or region if it is active. + +With ARG, interactively pick a face. Only highlights using the chosen +face will be removed." + (declare (interactive-only t)) + (interactive "P") + (let ((bounds (if (use-region-p) + (region-bounds) + `((,(pos-bol) . ,(pos-eol))))) + (face (when arg + (highlighter--read-face-name + "Clear highlights using face" + #'highlighter--used-face-p)))) + (highlighter-unmark-region bounds face)) + (when (region-active-p) + (deactivate-mark))) + +(defun highlighter-mark-region (bounds &optional face) + "Highlight text in the buffer within BOUNDS. +BOUNDS uses the same format as returned by `region-bounds'. + +Text is highlighted using the face specified by `highlighter-default-face'. + +If FACE is nil or omitted, `highlighter-default-face' is used." + (dolist (x bounds) (highlighter--mark-region (car x) (cdr x) face))) + +(defun highlighter-unmark-region (bounds &optional face) + "Remove highlights in the buffer within BOUNDS. +BOUNDS uses the same format as returned by `region-bounds'. + +If FACE is non-nil, only remove highlights using FACE." + (dolist (x bounds) (highlighter--unmark-region (car x) (cdr x) face))) + +(defun highlighter--mark-region (beg end &optional face) + (let ((ov (make-overlay beg end nil :front-advance)) + (face (or face highlighter-default-face 'match))) + (overlay-put ov 'priority 1) + (overlay-put ov 'face face) + (overlay-put ov 'evaporate t) + (overlay-put ov 'highlighter--mark-p t) + (overlay-put ov 'highlighter--face face))) + +(defun highlighter--unmark-region (beg end &optional face) + (if face + (remove-overlays beg end 'highlighter--face face) + (remove-overlays beg end 'highlighter--mark-p t))) + +(defun highlighter-unmark-buffer (arg) + "Remove highlights in the buffer. + +With ARG, interactively pick a face. Only highlights using the chosen +face will be removed." + (declare (interactive-only t)) + (interactive "P") + (let ((face (when arg + (highlighter--read-face-name + "Clear highlights using face" + #'highlighter--used-face-p)))) + (highlighter--unmark-region (point-min) (point-max) face))) + +(defun highlighter--read-face-name (prompt face-predicate) + (let (default defaults) + (let ((prompt (format "%s: " prompt)) + (completion-extra-properties + `(:affixation-function + ,(lambda (faces) + (mapcar + (lambda (face) + (list face + (concat (propertize read-face-name-sample-text + 'face face) + "\t") + "")) + faces)))) + aliasfaces nonaliasfaces faces) + ;; Build up the completion tables. + (mapatoms (lambda (s) + (when (apply face-predicate s nil) + (if (get s 'face-alias) + (push (symbol-name s) aliasfaces) + (push (symbol-name s) nonaliasfaces))))) + (let ((face (completing-read + prompt + (completion-table-in-turn nonaliasfaces aliasfaces) + nil t nil 'face-name-history defaults))) + (when (facep face) (if (stringp face) + (intern face) + face)))))) + +(defun highlighter--used-face-p (face) + (seq-filter (lambda (ov) (eq face (overlay-get ov 'highlighter--face))) + (overlays-in (point-min) (point-max)))) + +(provide 'highlighter) diff --git a/.config/emacs/site-lisp/live-jq.el b/.config/emacs/site-lisp/live-jq.el new file mode 100644 index 0000000..f8a0a7f --- /dev/null +++ b/.config/emacs/site-lisp/live-jq.el @@ -0,0 +1,101 @@ +;; TODO: ‘defcustom’ this +(defvar live-jq-major-mode + (cond ((fboundp #'json-ts-mode) #'json-ts-mode) + ((fboundp #'json-mode) #'json-mode)) + "TODO") + +(defvar live-jq--input-buffer nil + "The buffer containing the original JSON data.") + +(defvar live-jq--preview-buffer "*JQ Preview*" + "The buffer showing the live jq results.") + +(defvar live-jq--last-query "") + +(defun live-jq--get-json-input () + "Return the contents of the input buffer as a string." + (with-current-buffer live-jq--input-buffer + (buffer-substring-no-properties (point-min) (point-max)))) + +(defun live-jq--run-jq (query) + "Run jq QUERY on the input buffer's content and return result string or nil on error." + (let ((json-input (live-jq--get-json-input))) + (with-temp-buffer + (insert json-input) + (let ((exit-code (call-process-region + (point-min) (point-max) + "jq" :delete t nil "--tab" query))) + (when (zerop exit-code) + (buffer-string)))))) + +(defun live-jq--render-jq-preview (query) + "Update preview buffer with the result or error of jq QUERY." + (let* ((preview-buffer (get-buffer-create live-jq--preview-buffer)) + (json-input (live-jq--get-json-input)) + (inhibit-read-only t)) + (with-current-buffer preview-buffer + (erase-buffer) + (condition-case err + (with-temp-buffer + (insert json-input) + (let ((exit-code (call-process-region + (point-min) (point-max) + "jq" nil preview-buffer nil "--tab" query))) + (when (not (zerop exit-code)) + (erase-buffer) + (insert "%s\n%s" + (propertize (format "jq error (exit %d): %s" exit-code query) + 'face 'error) + json-input)))) + (error + (insert "%s\n%s" + (propertize (format "Error: %s" err) 'face 'error) + input-json))) + (goto-char (point-min)) + (when live-jq-major-mode + (funcall live-jq-major-mode)))) + (display-buffer live-jq--preview-buffer)) + +(defun live-jq--minibuffer-update () + "Update preview as user types." + (let ((query (minibuffer-contents))) + (unless (equal query live-jq--last-query) + (setq live-jq--last-query query) + (live-jq--render-jq-preview query)))) + +;;;###autoload +(defun live-jq () + "Prompt for a jq query, show live results, and replace buffer on confirmation." + (interactive) + (unless (executable-find "jq") + (user-error "`jq' not found in PATH.")) + + (setq live-jq--input-buffer (current-buffer)) + (setq live-jq--last-query "") + + ;; Clean up preview buffer if user cancels with C-g + (let ((minibuffer-setup-hook + (list (lambda () + ;; Add post-command-hook for live preview + (add-hook 'post-command-hook #'live-jq--minibuffer-update nil t) + ;; Add abort cleanup + (add-hook 'minibuffer-exit-hook + (lambda () + (when (get-buffer live-jq--preview-buffer) + (kill-buffer live-jq--preview-buffer))) + nil t))))) + (let ((query (read-from-minibuffer (format-prompt "Query" nil)))) + (unwind-protect + (let ((result (live-jq--run-jq query))) + (if result + (with-current-buffer live-jq--input-buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (insert result)) + (message "jq applied.")) + (user-error "Invalid jq query: see *jq-preview* for details"))) + ;; Cleanup preview buffer after any outcome + (when (get-buffer live-jq--preview-buffer) + (kill-buffer live-jq--preview-buffer)))))) + +(provide 'live-jq) diff --git a/.config/emacs/templates b/.config/emacs/templates index 6749183..ad4e97a 100644 --- a/.config/emacs/templates +++ b/.config/emacs/templates @@ -27,4 +27,12 @@ emacs-lisp-mode (buffer-name))) " --- " p " -*- lexical-binding: t; -*-" n n q) -(section "\f" n ";;; " p n n q)
\ No newline at end of file +(section "\f" n ";;; " p n n q) + +vue-ts-mode + +(fcrow + > "<div class=\"fc-row\">" + n> "<label>{{ _('" p "') }}</label>" + n> q + n> "</div>")
\ No newline at end of file diff --git a/.config/eww/eww.yuck b/.config/eww/eww.yuck index e356959..72f0b06 100644 --- a/.config/eww/eww.yuck +++ b/.config/eww/eww.yuck @@ -14,7 +14,10 @@ `stdbuf -oL scripts/workspace-list-listener`) (deflisten datetime - `stdbuf -oL scripts/datetime-listener`) + `LANG=sv_SE.UTF-8 stdbuf -oL scripts/datetime-listener`) + +(deflisten timer-1 + `stdbuf -oL scripts/timer-listener`) (deflisten brightness `stdbuf -oL scripts/backlight-listener`) @@ -26,7 +29,7 @@ `stdbuf -oL scripts/keymap-listener`) (deflisten emails :initial '{"icon":"","text":""}' - `stdbuf -oL scripts/email-listener`) + `LANG=sv_SE.UTF-8 stdbuf -oL scripts/email-listener`) ;; Pollers (defpoll user :interval "69h" @@ -64,7 +67,9 @@ (workspaces :monitor monitor)) (defwidget middle [] - (clock)) + (box :space-evenly false + (clock) + (timer))) (defwidget end [] (box :halign "end" @@ -89,6 +94,10 @@ (defwidget clock [] datetime) +(defwidget timer [] + (box :class "timer" + (label :text timer-1))) + (defwidget wireless [] (eventbox :onhover "eww update wireless_rev=true" diff --git a/.config/eww/scripts/datetime-listener b/.config/eww/scripts/datetime-listener index 1cd8e39..47d7031 100755 --- a/.config/eww/scripts/datetime-listener +++ b/.config/eww/scripts/datetime-listener @@ -1,12 +1,22 @@ #!/bin/sh -while : +veckaord() +{ + case "${LANG:-LC_ALL}" in + sv_*.*) + echo vecka ;; + pt_*.*) + echo semana ;; + *) + echo Week ;; + esac +} + +stdbuf -oL -- tiktok \ + "%A, %-d %B %Y, $(veckaord) %V — %T %Z" \ + TZ=Europe/Helsinki ' (%H:%M %Z,' \ + TZ=Pacific/Auckland ' %H:%M %Z)' \ +| while read line do - # { - # date +'%A, %-d of %B %Y — %T %Z' - # TZ='Asia/Kolkata' date +'(%T %Z)' - # } | paste -sd ' ' - date +'%A, %-d of %B %Y — %H:%M %Z' - sleep 10 - # sleep "$(date +'%s.%N' | dc -e '? d 1 + 1 / r - p')" -done | ordinal -p1 + echo "$line" | ordinal -p '^\w+, (\d+)' +done | sed 's/^./\U&/g' diff --git a/.config/eww/scripts/email-listener b/.config/eww/scripts/email-listener index 6889345..b5c1cf3 100755 --- a/.config/eww/scripts/email-listener +++ b/.config/eww/scripts/email-listener @@ -8,6 +8,17 @@ export NOTIFY_LONG=email readonly CACHE="${XDG_CACHE_HOME:=$HOME/.cache}/email-listener" touch "$CACHE" +T() +{ + en="$1" + sv="$2" + shift 2 + case "${LC_ALL:-$LANG}" in + en_*) printf "$en" "$@" ;; + sv_*) printf "$sv" "$@" + esac +} + count() { local cur=`find "$MAILDIR"/*/Inbox/cur -type f | wc -l` @@ -16,16 +27,16 @@ count() case $sum in 0) - printf 'icon=\ntext=No Email' + T 'icon=\ntext=No Emails' 'icon=\ntext=Inga meddelanden' ;; 1) - printf 'icon=\ntext=1 Email' + T 'icon=\ntext=1 Email' 'icon=\ntext=1 meddelande' ;; *) - printf 'icon=\ntext=%d Emails' $sum + T 'icon=\ntext=%d Emails' 'icon=\ntext=%d meddelanden' $sum ;; esac - [ $new -gt 0 ] && printf ' (%d Unread)' $new + [ $new -gt 0 ] && T ' (%d Unread)' ' (%d ölasta)' $new echo echo $new >"$CACHE" } @@ -42,12 +53,14 @@ count() case $((new_mails - prev)) in 1) - title='New Email' - desc='1 new email has been received' + title="$(T 'New Email' 'Nytt meddelande')" + desc="$(T '1 new email has been received' + '1 nytt meddelande har mottagits')" ;; *) - title='New Emails' - desc="$new_mails new emails have been received" + title="$(T 'New Emails' 'Nya meddelanden')" + desc="$(T "$new_mails new emails have been received" + "$new_mails nya meddelanden har mottagits")" ;; esac @@ -67,4 +80,4 @@ do count | jo ;; esac -done +done
\ No newline at end of file diff --git a/.config/eww/scripts/timer-listener b/.config/eww/scripts/timer-listener new file mode 100755 index 0000000..c604295 --- /dev/null +++ b/.config/eww/scripts/timer-listener @@ -0,0 +1,16 @@ +#!/bin/sh + +cd "${0%/*}" + +while : +do + while read line <../data/timer.pipe + do + if [ "$line" = "" ] + then + echo + else + printf ' %s\n' "$line" + fi + done +done diff --git a/.config/hypr/hyprland.conf.in b/.config/hypr/hyprland.conf.in index dd22153..17631ff 100644 --- a/.config/hypr/hyprland.conf.in +++ b/.config/hypr/hyprland.conf.in @@ -19,7 +19,7 @@ input { accel_profile = "adaptive" follow_mouse = 1 kb_layout = mango, mango, mango, us - kb_variant = basic, swedish, german, basic + kb_variant = swedish, basic, german, basic kb_options = lv3:switch,compose:ralt repeat_delay = 360 # 180 repeat_rate = 35 @@ -70,8 +70,7 @@ animations { } gestures { - workspace_swipe = yes - workspace_swipe_fingers = 4 + gesture = 4, horizontal, workspace workspace_swipe_distance = 400 workspace_swipe_min_speed_to_force = 10 } diff --git a/.local/bin/Makefile b/.local/bin/Makefile new file mode 100644 index 0000000..2f659f9 --- /dev/null +++ b/.local/bin/Makefile @@ -0,0 +1,30 @@ +TEXTDOMAINDIR=/usr/local/share/locale + +all: + +extract: + mkdir -p po/sv + for x in vlt; \ + do \ + xgettext -L Shell -k_ --from-code=UTF-8 -o "po/$$x.pot" "$$x"; \ + for l in sv_SE; \ + do \ + d="$${l%_*}"; \ + if [ -e "po/$$d/$$x.po" ]; \ + then \ + msgmerge -U "po/$$d/$$x.po" "po/$$x.pot"; \ + else \ + msginit -l "$$l.UTF-8" -i "po/$$x.pot" -o "po/$$d/$$x.po"; \ + fi; \ + done; \ + done + +compile: + mkdir -p "$(TEXTDOMAINDIR)/sv/LC_MESSAGES" + for l in sv; \ + do \ + for x in vlt; \ + do \ + msgfmt "po/$$l/$$x.po" -o "$(TEXTDOMAINDIR)/$$l/LC_MESSAGES/$$x.mo"; \ + done; \ + done diff --git a/.local/bin/alarm b/.local/bin/alarm new file mode 100755 index 0000000..716ea1b --- /dev/null +++ b/.local/bin/alarm @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +export NOTIFY_LONG=alarm +export NOTIFY_SHORT="${0##*/}" + +readonly PIPE="${XDG_CONFIG_HOME:-$HOME/.config}/eww/data/timer.pipe" +exec >"$PIPE" + +if [ $# -gt 1 ] +then + >&2 printf 'Usage: %s [duration]\n' "${0##*/}" + exit 1 +fi + +stdbuf -oL -- patience -f '%m:%02S' "${1:-m=1}" +echo '' +exec >&- + +notify 'Timer Complete' 'The global timer has reached its end' +aplay -q ~/media/sfx/alarm.wav diff --git a/.local/bin/po/sv/vlt.po b/.local/bin/po/sv/vlt.po new file mode 100644 index 0000000..a952f3d --- /dev/null +++ b/.local/bin/po/sv/vlt.po @@ -0,0 +1,178 @@ +# Swedish translations for PACKAGE package +# Svenska översättningar för paket PACKAGE. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Thomas Voss <mail@thomasvoss.com>, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-01 22:58+0200\n" +"PO-Revision-Date: 2025-09-01 22:45+0200\n" +"Last-Translator: Thomas Voss <mail@thomasvoss.com>\n" +"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: vlt:25 +msgid "Invalid input" +msgstr "Ogiltig inmatning" + +#: vlt:26 +msgid "Empty strings do not constitute valid input" +msgstr "Tomma strängar utgör ej giltig indata" + +#: vlt:34 +#, sh-printf-format +msgid "" +"Usage: %s add [-c]\n" +" %s edit [-c]\n" +" %s get\n" +" %s raw category password\n" +" %s rm [-c]\\n" +msgstr "" +"Användning: %s add [-c]\n" +" %s edit [-c]\n" +" %s get\n" +" %s raw kategori lösenord\n" +" %s rm [-c]\\n" + +#: vlt:97 +msgid "Password name" +msgstr "Lösenordsnamn" + +#: vlt:102 +msgid "Failed to add password" +msgstr "Misslyckades med att skapa lösenord" + +#: vlt:103 +#, sh-printf-format +msgid "The password ‘%s’ already exists" +msgstr "Lösenordet ”%s” finns redan" + +#: vlt:109 vlt:259 +msgid "Secret key" +msgstr "Hemlig nyckel" + +#: vlt:110 vlt:260 +msgid "Digits" +msgstr "Siffror" + +#: vlt:111 vlt:261 +msgid "Period" +msgstr "Period" + +#: vlt:125 vlt:275 +msgid "2FA key added" +msgstr "2FA-nyckel tillagd" + +#: vlt:126 vlt:276 +#, sh-printf-format +msgid "The 2FA key ‘%s’ was added with the digit length ‘%d’ and period ‘%d’" +msgstr "2FA-nyckeln ”%s” lades till med siffralägden ”%d” och perioden ”%d”" + +#: vlt:128 vlt:278 +msgid "Password" +msgstr "Lösenord" + +#: vlt:133 +msgid "Password added" +msgstr "Lösenord tillagt" + +#: vlt:134 +#, sh-printf-format +msgid "The password ‘%s’ was added to the category ‘%s’" +msgstr "Lösenordet ”%s” tillagt till kategorin ”%s”" + +#: vlt:142 +msgid "Category to create" +msgstr "Kategori att skapa" + +#: vlt:145 +msgid "Failed to Create Category" +msgstr "Misslyckades med att skapa kategori" + +#: vlt:145 +#, sh-printf-format +msgid "The category ‘%s’ already exists" +msgstr "Kategorin ”%s” finns redan" + +#: vlt:153 +msgid "Category created" +msgstr "Kategori skapad" + +#: vlt:153 +#, sh-printf-format +msgid "The password category ‘%s’ was created" +msgstr "Lösenordskategorin ”%s” skapades" + +#: vlt:177 +msgid "2FA code copied to the clipboard" +msgstr "2FA-kod kopierad till urklipp" + +#: vlt:178 +#, sh-printf-format +msgid "The 2FA code for ‘%s’ was copied to the clipboard" +msgstr "2FA-koden för ”%s” har kopierats till urklipp" + +#: vlt:183 +msgid "Password copied to the clipboard" +msgstr "Lösenord kopierat till urklipp" + +#: vlt:184 +#, sh-printf-format +msgid "The password for ‘%s’ was copied to the clipboard" +msgstr "Lösenordet ”%s” har kopierats till urklipp" + +#: vlt:213 +msgid "Password removed" +msgstr "Lösenord borttaget" + +#: vlt:214 +#, sh-printf-format +msgid "The password ‘%s’ was removed from the category ‘%s’" +msgstr "Lösenordet ”%s” har tagits bort från kategorin ”%s”" + +#: vlt:225 +msgid "Failed to remove category" +msgstr "Kategorin kunde ej tas bort" + +#: vlt:225 +#, sh-printf-format +msgid "The category ‘%s’ is not empty" +msgstr "Kategorin ”%s” är ej tom" + +#: vlt:232 +msgid "Category removed" +msgstr "Kategori borttagen" + +#: vlt:232 +#, sh-printf-format +msgid "The category ‘%s’ was removed" +msgstr "Kategorin ”%s” har tagits bort" + +#: vlt:282 +msgid "Password edited" +msgstr "Lösenord redigerat" + +#: vlt:283 +#, sh-printf-format +msgid "The password ‘%s’ in the category ‘%s’ was edited" +msgstr "Lösenordet ”%s” i kategorin ”%s” har redigerats" + +#: vlt:292 +msgid "Category name" +msgstr "Kategorinamn" + +#: vlt:297 +msgid "Category edited" +msgstr "Kategori redigerad" + +#: vlt:297 +#, sh-printf-format +msgid "The category ‘%s’ was renamed" +msgstr "Kategorin ”%s” har bytt namn" diff --git a/.local/bin/po/vlt.pot b/.local/bin/po/vlt.pot new file mode 100644 index 0000000..c93caf5 --- /dev/null +++ b/.local/bin/po/vlt.pot @@ -0,0 +1,172 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-01 22:58+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: vlt:25 +msgid "Invalid input" +msgstr "" + +#: vlt:26 +msgid "Empty strings do not constitute valid input" +msgstr "" + +#: vlt:34 +#, sh-printf-format +msgid "" +"Usage: %s add [-c]\n" +" %s edit [-c]\n" +" %s get\n" +" %s raw category password\n" +" %s rm [-c]\\n" +msgstr "" + +#: vlt:97 +msgid "Password name" +msgstr "" + +#: vlt:102 +msgid "Failed to add password" +msgstr "" + +#: vlt:103 +#, sh-printf-format +msgid "The password ‘%s’ already exists" +msgstr "" + +#: vlt:109 vlt:259 +msgid "Secret key" +msgstr "" + +#: vlt:110 vlt:260 +msgid "Digits" +msgstr "" + +#: vlt:111 vlt:261 +msgid "Period" +msgstr "" + +#: vlt:125 vlt:275 +msgid "2FA key added" +msgstr "" + +#: vlt:126 vlt:276 +#, sh-printf-format +msgid "The 2FA key ‘%s’ was added with the digit length ‘%d’ and period ‘%d’" +msgstr "" + +#: vlt:128 vlt:278 +msgid "Password" +msgstr "" + +#: vlt:133 +msgid "Password added" +msgstr "" + +#: vlt:134 +#, sh-printf-format +msgid "The password ‘%s’ was added to the category ‘%s’" +msgstr "" + +#: vlt:142 +msgid "Category to create" +msgstr "" + +#: vlt:145 +msgid "Failed to Create Category" +msgstr "" + +#: vlt:145 +#, sh-printf-format +msgid "The category ‘%s’ already exists" +msgstr "" + +#: vlt:153 +msgid "Category created" +msgstr "" + +#: vlt:153 +#, sh-printf-format +msgid "The password category ‘%s’ was created" +msgstr "" + +#: vlt:177 +msgid "2FA code copied to the clipboard" +msgstr "" + +#: vlt:178 +#, sh-printf-format +msgid "The 2FA code for ‘%s’ was copied to the clipboard" +msgstr "" + +#: vlt:183 +msgid "Password copied to the clipboard" +msgstr "" + +#: vlt:184 +#, sh-printf-format +msgid "The password for ‘%s’ was copied to the clipboard" +msgstr "" + +#: vlt:213 +msgid "Password removed" +msgstr "" + +#: vlt:214 +#, sh-printf-format +msgid "The password ‘%s’ was removed from the category ‘%s’" +msgstr "" + +#: vlt:225 +msgid "Failed to remove category" +msgstr "" + +#: vlt:225 +#, sh-printf-format +msgid "The category ‘%s’ is not empty" +msgstr "" + +#: vlt:232 +msgid "Category removed" +msgstr "" + +#: vlt:232 +#, sh-printf-format +msgid "The category ‘%s’ was removed" +msgstr "" + +#: vlt:282 +msgid "Password edited" +msgstr "" + +#: vlt:283 +#, sh-printf-format +msgid "The password ‘%s’ in the category ‘%s’ was edited" +msgstr "" + +#: vlt:292 +msgid "Category name" +msgstr "" + +#: vlt:297 +msgid "Category edited" +msgstr "" + +#: vlt:297 +#, sh-printf-format +msgid "The category ‘%s’ was renamed" +msgstr "" diff --git a/.local/bin/vlt b/.local/bin/vlt index 20219d8..9bf7f60 100755 --- a/.local/bin/vlt +++ b/.local/bin/vlt @@ -5,25 +5,37 @@ set -e export NOTIFY_LONG=vault export NOTIFY_SHORT="${0##*/}" +export TEXTDOMAIN=vlt +export TEXTDOMAINDIR=/usr/local/share/locale + +. gettext.sh +_() +{ + fmt="$1" + shift + printf "$(eval_gettext "$fmt")" "$@" +} + sanitize() { s="${1#"${1%%[![:space:]]*}"}" s="${s%"${s##*[![:space:]]}"}" if [ "${#s}" -eq 0 ] then - notify 'Invalid Input' 'Empty strings do not constitute valid input' + notify "$(_ 'Invalid input')" \ + "$(_ 'Empty strings do not constitute valid input')" exit 1 fi } usage() { - cat <<-EOF >&2 - Usage: ${0##*/} add [-c] - ${0##*/} edit [-c] - ${0##*/} get - ${0##*/} rm [-c] - EOF + s="${0##*/}" + _ "Usage: %s add [-c] + %s edit [-c] + %s get + %s raw category password + %s rm [-c]\n" "$s" "$s" "$s" "$s" "$s" exit 1 } @@ -82,20 +94,21 @@ add() c="$(xecho "$data" | jq -r 'keys | .[]' | osel)" - prompt 'Password name' + prompt "$(_ 'Password name')" n="$s" xecho "$data" \ | jq -e --arg c "$c" --arg n "$n" '.[$c] | has($n) | not' >/dev/null || { - notify 'Failed To Add Password' "The password ‘$n’ already exists" + notify "$(_ 'Failed to add password')" \ + "$(_ "The password ‘%s’ already exists" "$n")" exit 1 } if [ "$VAULT_2FA" = "$c" ] then - xprompt 'Secret key'; k="$s" - prompt 'Digits'; d="$s" - prompt 'Period' + xprompt "$(_ 'Secret key')"; k="$s" + prompt "$(_ 'Digits')"; d="$s" + prompt "$(_ 'Period')" xecho "$data" \ | jq --arg c "$c" \ @@ -109,16 +122,16 @@ add() "period": ($p | tonumber) }}' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify '2FA Key Added' \ - "The 2FA key ‘$n’ was added with the digit length ‘$d’ and period ‘$p’" + [ ! -t 2 ] && notify "$(_ '2FA key added')" \ + "$(_ "The 2FA key ‘%s’ was added with the digit length ‘%d’ and period ‘%d’" "$n" "$d" "$p")" else - xprompt 'Password' + xprompt "$(_ 'Password')" xecho "$data" \ | jq --arg c "$c" --arg n "$n" --arg s "$s" '.[$c] += {($n): $s}' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify 'Password Added' \ - "The password ‘$n’ was added to the category ‘$c’" + [ ! -t 2 ] && notify "$(_ 'Password added')" \ + "$(_ "The password ‘%s’ was added to the category ‘%s’" "$n" "$c")" fi } @@ -126,10 +139,10 @@ add_c() { readonly data="$(enchive extract <"$VAULT")" - prompt 'Category to create' + prompt "$(_ 'Category to create')" xecho "$data" | jq -e --arg s "$s" 'has($s) | not' >/dev/null || { - notify 'Failed To Create Category' "The category ‘$s’ already exists" + notify "$(_ 'Failed to Create Category')" "$(_ "The category ‘%s’ already exists" "$s")" exit 1 } @@ -137,7 +150,7 @@ add_c() | jq --arg s "$s" '. + {($s): {}}' \ | enchive archive >"$VAULT" [ ! -t 2 ] && \ - notify 'Category Created' "The password category ‘$s’ was created" + notify "$(_ 'Category created')" "$(_ "The password category ‘%s’ was created" "$s")" } get() @@ -161,14 +174,14 @@ get() ')" \ | wl-copy -no \ && [ ! -t 2 ] \ - && notify '2FA Code Copied To The Clipboard' \ - "The 2FA code for ‘$o’ was copied to the clipboard" + && notify "$(_ '2FA code copied to the clipboard')" \ + "$(_ "The 2FA code for ‘%s’ was copied to the clipboard" "$o")" else jq -r --arg c "$c" --arg o "$o" '.[$c] | .[$o]' \ | wl-copy -no \ && [ ! -t 2 ] \ - && notify 'Password Copied To The Clipboard' \ - "The password for ‘$o’ was copied to the clipboard" + && notify "$(_ 'Password copied to the clipboard')" \ + "$(_ "The password for ‘%s’ was copied to the clipboard" "$o")" fi } @@ -197,8 +210,8 @@ rm_() xecho "$data" \ | jq --arg c "$c" --arg n "$n" 'del(.[$c] | .[$n])' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify 'Removed Password' \ - "The password ‘$n’ was removed from the category ‘$c’" + [ ! -t 2 ] && notify "$(_ 'Password removed')" \ + "$(_ "The password ‘%s’ was removed from the category ‘%s’" "$n" "$c")" } rm_c() @@ -209,14 +222,14 @@ rm_c() xecho "$data" \ | jq -e --arg c "$c" '.[$c] | length == 0' >/dev/null || { - notify 'Failed To Remove Category' "The category ‘$c’ is not empty" + notify "$(_ 'Failed to remove category')" "$(_ "The category ‘%s’ is not empty" "$c")" exit 1 } xecho "$data" \ | jq --arg c "$c" 'del(.[$c])' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify 'Removed Category' "The category ‘$c’ was removed" + [ ! -t 2 ] && notify "$(_ 'Category removed')" "$(_ "The category ‘%s’ was removed" "$c")" } edit() @@ -243,9 +256,9 @@ edit() if [ "$VAULT_2FA" = "$c" ] then - xprompt 'Secret key'; k="$s" - prompt 'Digits'; d="$s" - prompt 'Period' + xprompt "$(_ 'Secret key')"; k="$s" + prompt "$(_ 'Digits')"; d="$s" + prompt "$(_ 'Period')" xecho "$data" \ | jq --arg c "$c" \ @@ -259,15 +272,15 @@ edit() "period": ($p | tonumber) }}' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify '2FA Key Added' \ - "The 2FA key ‘$n’ was added with the digit length ‘$d’ and period ‘$p’" + [ ! -t 2 ] && notify "$(_ '2FA key added')" \ + "$(_ "The 2FA key ‘%s’ was added with the digit length ‘%d’ and period ‘%d’" "$n" "$d" "$p")" else - xprompt 'Password' + xprompt "$(_ 'Password')" xecho "$data" \ | jq --arg c "$c" --arg n "$n" --arg s "$s" '.[$c] += {($n): $s}' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify 'Password Edit' \ - "The password ‘$n’ in the category ‘$c’ was changed" + [ ! -t 2 ] && notify "$(_ 'Password edited')" \ + "$(_ "The password ‘%s’ in the category ‘%s’ was edited" "$n" "$c")" fi } @@ -276,12 +289,12 @@ edit_c() readonly data="$(enchive extract <"$VAULT")" c="$(xecho "$data" | jq -r 'keys | .[]' | osel)" - prompt 'Category name' + prompt "$(_ 'Category name')" xecho "$data" \ | jq --arg o "$c" --arg n "$s" \ 'with_entries(if .key == $o then .key = $n else . end)' \ | enchive archive >"$VAULT" - [ ! -t 2 ] && notify 'Category Edit' "The category ‘$c’ was renamed" + [ ! -t 2 ] && notify "$(_ 'Category edited')" "$(_ "The category ‘%s’ was renamed" "$c")" } raw() diff --git a/.local/share/applications/Vault.desktop b/.local/share/applications/Vault.desktop index 76ef164..f296257 100644 --- a/.local/share/applications/Vault.desktop +++ b/.local/share/applications/Vault.desktop @@ -4,7 +4,6 @@ Categories=Utility;Database;TextTools;Archiving;Security Comment=Store and Retrieve Passwords Exec=vlt get GenericName=Password Store -Icon=/home/thomas/down/lock.png Keywords=Password Name=Vault Terminal=false diff --git a/.local/share/vault/vault.sec b/.local/share/vault/vault.sec Binary files differindex 5f1a25f..e341e4b 100644 --- a/.local/share/vault/vault.sec +++ b/.local/share/vault/vault.sec diff --git a/.xkb/symbols/mango b/.xkb/symbols/mango index f2ab38f..d62ede2 100644 --- a/.xkb/symbols/mango +++ b/.xkb/symbols/mango @@ -8,9 +8,8 @@ xkb_symbols "basic" { key <AE01> { [ exclam, 1, exclam, 1 ] }; key <AE02> { [ at, 2, at, 2 ] }; key <AE03> { [ numbersign, 3, numbersign, 3 ] }; - key <AE04> { [ EuroSign, 4, dollar, 4 ] }; key <AE04> { [ dollar, 4, EuroSign, EuroSign ] }; - key <AE05> { [ percent, 5, dollar, 5 ] }; + key <AE05> { [ percent, 5, EuroSign, EuroSign ] }; key <AE06> { [ asciicircum, 6, asciicircum, 6 ] }; key <AE07> { [ ampersand, 7, logicaland, 7 ] }; key <AE08> { [ asterisk, 8, U22C5, 8 ] }; // ⋅ @@ -47,16 +46,29 @@ xkb_symbols "german" { include "mango(basic)" name[Group1] = "Mango (German)"; - key <AC10> { [ NoSymbol, NoSymbol, singlelowquotemark, doublelowquotemark ] }; - key <AC11> { [ NoSymbol, NoSymbol, leftsinglequotemark, leftdoublequotemark ] }; + key <AD11> { [ udiaeresis, Udiaeresis, bracketleft, braceleft ] }; + key <AD12> { [ ssharp, U1E9E, bracketright, braceright ] }; // ẞ + key <AC10> { [ odiaeresis, Odiaeresis, semicolon, colon ] }; + key <AC11> { [ adiaeresis, Adiaeresis, apostrophe, quotedbl ] }; + + key <AC08> { [ k, K, singlelowquotemark, doublelowquotemark ] }; + key <AC09> { [ l, L, leftsinglequotemark, leftdoublequotemark ] }; }; partial xkb_symbols "swedish" { include "mango(basic)" - name[Group1] = "Mango (Swedish)"; + name[Group1] = "Mango (Swedish)"; + + key <AD11> { [ aring, Aring, bracketleft, braceleft ] }; + key <AD12> { [ U0301, U0300, bracketright, braceright ] }; // ◌́ and ◌̀ + key <AC10> { [ odiaeresis, Odiaeresis, semicolon, colon ] }; + key <AC11> { [ adiaeresis, Adiaeresis, apostrophe, quotedbl ] }; + + key <AC08> { [ k, K, rightsinglequotemark, rightdoublequotemark ] }; + key <AC09> { [ l, L, rightsinglequotemark, rightdoublequotemark ] }; - key <AD11> { [ aring, Aring, bracketleft, braceleft ] }; - key <AC10> { [ odiaeresis, Odiaeresis, semicolon, colon ] }; - key <AC11> { [ adiaeresis, Adiaeresis, apostrophe, quotedbl ] }; + // Danska och norska bokstäver på nivå 3 + key <AC01> { [ a, A, ae, AE ] }; + key <AD09> { [ o, O, oslash, Oslash ] }; }; |