diff options
| author | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-06-22 02:05:44 +0200 | 
|---|---|---|
| committer | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-06-22 02:05:44 +0200 | 
| commit | 36d2972c60ec86b873fa496d1f5ea95cf748cf49 (patch) | |
| tree | a6d6750fa17c2964cd241afa8e963cac6106b390 | |
| parent | 4914b43f642e2772a140a8f9b1f26b4e555ed88b (diff) | |
| parent | 32256e087aaf7744348a5ba33e802d5c8d9d97dd (diff) | |
Merge branch 'main' of db:dotfiles
61 files changed, 2948 insertions, 60 deletions
| diff --git a/bin/common/askpass b/bin/common/askpass new file mode 100755 index 0000000..2725dbf --- /dev/null +++ b/bin/common/askpass @@ -0,0 +1,13 @@ +#!/bin/sh + +# We can figure out the password for the key based on $1 +# which is in the following form: +#	Enter passphrase for key 'path/to/key': +# The point is to retrieve the path and use the final name of the key +# find the according password. +key="$(printf '%s\n' "$1" | +	cut -f 2 -d \' | +	awk -F '/' '{print $NF}')" +pass="keys/$(hostname)/ssh/$key" + +pass show "$pass" | head -n 1 diff --git a/bin/common/gt b/bin/common/gt new file mode 100755 index 0000000..c679b23 --- /dev/null +++ b/bin/common/gt @@ -0,0 +1,121 @@ +#!/bin/sh + +# Git Trach, track the state of multiple repos from a single file. + +# dependencies: +# - git +# - $EDITOR: -e   + +repos=$HOME/sync/share/git-track.txt +# prevent file not found errors +touch "$repos" + +help() { +    >&2 cat <<EOF +usage: gt [OPTION] +-a PATH         add repo  +-s              update and show status of each repo +-c COMMAND      run 'git COMMAND'  in each repo +-h              show this help +-l              list repos +-e 		edit repos in $EDITOR +EOF +} + +# fetch repository prettily, outputs nothing if failed +fetch() { +	# fetch with one-line printing of progress +	git fetch --progress 2>/dev/null | while read -r line  +        	# \r\033[0K : clear current line +        	do >&2 printf '\r\033[0K%s' "$line" +        	done +} + +# Print repositories prettily +# This function function prints animations (eg. clearing the line) +# to stderr and the final status line is outputted to stdout. +status() { +    while read -r repo +    do +        repo_pretty="$(printf '%s' "$repo" | sed "s@$HOME@~@" )" + +        # absolute path +	cd "$repo"  + +	# replace line with status  +	>&2 printf '\r\033[0K' + +	status="$(git status --porcelain 2> /dev/null | awk '{print $1}' | uniq | tr -d '\n')" +	remote="$(git branch -v 2>/dev/null | +		sed '/^*/!d;s/ahead/↑/;s/behind/↓/;s/[^↓↑]*//g')" + +	printf '%s %s %s\n' "$repo_pretty" "$status" "$remote" +    done < "$repos" +} + +# run git command in each repo +# $1: command +repos_cmd() { +    while read -r repo +    do +        repo_pretty="$(printf '%s' "$repo" | sed "s@$HOME@~@" )" +        printf ''\''%s'\'' in %s' "$1" "$repo_pretty" +        ( +            cd "$repo"  +            git "$1" > /dev/null 2>&1 +            [ $? -gt 0 ] && s="x" || s="o" +            printf '\r\033[0K%s: %s\n' "$repo_pretty" "$s" +        ) +    done < "$repos" +} + +# no options +if [ -z "$1" ] +then +    help +    exit 1 +fi + +while getopts ":a:c:f:lshe" opt +do +	case "$opt" in +		a)  +			cd "$OPTARG" || exit 1 +			r="$(git rev-parse --show-toplevel)" +			[ "$r" ] || exit 2  + +			if grep "$r" "$repos" > /dev/null 2>&1 +			then +				>&2 printf 'added already.\n' +				exit 2 +			fi + +			printf '%s\n' "$r" >> "$repos"  + +			>&2 printf 'added.\n' ;; +	        c) f_command=1; f_arg="$OPTARG" ;; +		s) f_status=1 ;; +		l) cat "$repos" ;; +		e) $EDITOR "$repos" ;; +		f) repos="$OPTARG" ;; +		h) help ;; +		:) >&2 printf -- '-%s requires argument\n' "$OPTARG"; exit 1 ;; +		?) >&2 printf -- 'Invalid option: -%s\n' "$OPTARG"; exit 1 ;; +	esac +done + +# commands hereafter must happen in order + +[ "$(wc -l < "$repos")" -gt 0 ] || exit 0 + +if [ "$f_command" ] +then +	repos_cmd "$f_arg" +fi  + +if [ "$f_status" ]  +then +	status  +fi + +# eval "herbe $(status | sed 's/"/\"/g;s/.*/"&"/' | tr '\n' ' ')" diff --git a/bin/extra/aivpn b/bin/extra/aivpn new file mode 100755 index 0000000..841d926 --- /dev/null +++ b/bin/extra/aivpn @@ -0,0 +1,30 @@ +#!/bin/sh + +err() { printf "%s\n" "$@"; } + +if [ "$1" = "-k" ] +then +	pgrep -f -- "ssh.*-L.*vm" | xargs kill +	exit +fi + +err "I: Waiting for connectivity..." +while ! ssh -o ConnectTimeout=1 -o BatchMode=yes vm 2>&1 | grep "Permission denied" > /dev/null +do sleep 1 +done + + +export SSH_ASKPASS="sshpass" +export SSH_ASKPASS_REQUIRE="prefer" +export PASSWORD="zot/quickemu" + +err "I: Activating vpn" +ssh vm "rasdial \"vpn.student.ehb.be\"" + +keyadd ehb/ai +ssh -f -N -L 2222:10.2.160.41:22 vm + +keyadd ehb/vm_int +ssh -f -N -L 2223:10.2.160.9:22 vm +ssh -f -N -L 2224:10.2.160.10:22 vm +ssh -f -N -L 2225:10.2.160.11:22 vm diff --git a/bin/extra/clock b/bin/extra/clock new file mode 100755 index 0000000..bd6efd2 --- /dev/null +++ b/bin/extra/clock @@ -0,0 +1,64 @@ +#!/bin/sh + +clocks="${XDG_DATA_HOME:-$HOME}"/clocks.csv + +if [ ! -f "$clocks" ] +then +	printf 'start,end,message\n' > "$clocks" +fi + +# print clocks file prettily  +if [ "$1" = "-p" ] +then +	# empty +	[ "$(wc -l < "$clocks")" -eq 1 ] && exit + +	timefmt="%y%m%d-%T" +	IFS="," +	# skip csv header +	tail -n +2 "$clocks" |    +	while read -r start end message +	do +		printf "%s - %s | %s\n" "$(date -d "@$start" +"$timefmt" )" "$(date -d "@$end" +"$timefmt")" "$message" +	done +	exit +fi + +# edit clocks file in $EDITOR +if [ "$1" = "-e" ] +then +	$EDITOR "$clocks" +	exit +fi + +trap 'exit 0' INT # The proper way to exit + +while true +do +	>&2 printf ' > '  +	message="$(head -n 1)"  + +	[ "$message" ] || exit 1 +	printf '\033[1A' # move cursor up once: https://en.wikipedia.org/wiki/ANSI_escape_code + +	start_time="$(date +%s)" +	start_time_pretty="$(date -d "@$start_time" +%R)"  +	>&2 printf -- '\r%s- > %s' "$start_time_pretty" "$message" + +	# Wait for EOF +	cat > /dev/null 2>&1 + +	end_time="$(date +%s)" +	end_time_pretty="$(date -d "@$end_time" +%R)"  +	>&2 printf -- '\r%s-%s > %s\n' "$start_time_pretty" "$end_time_pretty" "$message" + +	if printf '%s' "$message" | grep ',' > /dev/null +	then +		# escape potential double quotes +		message="$(printf '%s' "$message" | sed -e 's/"/""/g')" +		message="\"$message\"" +	fi + +	# save clocked time and message +	printf '%s,%s,%s\n' "$start_time" "$end_time" "$message" >> "$clocks" +done diff --git a/bin/extra/ddsurf b/bin/extra/ddsurf new file mode 100755 index 0000000..a3ae0d1 --- /dev/null +++ b/bin/extra/ddsurf @@ -0,0 +1,4 @@ +#!/bin/sh +f="$(mktemp)" +awk '!x[$2]++' ~/.config/surf/history.txt > "$f" +mv "$f" ~/.config/surf/history.txt diff --git a/bin/extra/igdl b/bin/extra/igdl new file mode 100755 index 0000000..1973187 --- /dev/null +++ b/bin/extra/igdl @@ -0,0 +1,21 @@ +#!/bin/sh +lock="/tmp/igdl.lock" + +if [ -f "$lock" ] +then +	herbe "already downloading." +	exit 1 +fi + + +url="$(clipo)" +out="/tmp/igdl.mp4" + +touch "$lock" +herbe "igdl" "downloading: $url"  & +yt-dlp "$url" -o "$out" || rm "$lock" + +printf '%s' "$out" | clipp +herbe "igdl" "copied path." + +rm "$lock" diff --git a/bin/extra/mtr b/bin/extra/mtr new file mode 100755 index 0000000..486a9b8 --- /dev/null +++ b/bin/extra/mtr @@ -0,0 +1,23 @@ +#!/bin/sh + +list_categories() +{ +    cat <<EOF +music +anime +movies +shows +other +software +games +isos +books +EOF +} + +category="$(list_categories | commander -cl)" +[ "$category" ] || exit 1 + +transmission-remote debuc.com -a "$(clipo)" -w "/downloads/$category" + +notify-send "mtr" "added to <b>$category</b>" diff --git a/bin/extra/muz-sync b/bin/extra/muz-sync new file mode 100755 index 0000000..a81ef74 --- /dev/null +++ b/bin/extra/muz-sync @@ -0,0 +1,10 @@ +#!/bin/sh +trap "exit 1" INT + +music="$(xdg-user-dir MUSIC)" +>&2 printf "music: %s\n" "$music" + +# recursive, links, fuzzy, partial, progress +while ! rsync -rlyP --size-only db:/media/basilisk/music/sorted/ "$music" +do : +done diff --git a/bin/extra/myalscore.sh b/bin/extra/myalscore.sh new file mode 100755 index 0000000..ef68bcc --- /dev/null +++ b/bin/extra/myalscore.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +[ "$1" ] || exit 1 +query="$(printf '%s' "$*" | sed 's/\s/%20/g')" +curl -s "https://myanimelist.net/search/prefix.json?type=all&keyword=$query&v=1" \ +	-H 'Accept: application/json, text/javascript, */*; q=0.01' | +	jq -r '.categories[].items[] | [.payload.score, .name] | join(" ")'  diff --git a/bin/extra/spschedule b/bin/extra/spschedule new file mode 100755 index 0000000..b0dd70a --- /dev/null +++ b/bin/extra/spschedule @@ -0,0 +1,2 @@ +#!/bin/sh +curl -s 'https://subsplease.org/api/?f=schedule&tz=UTC' | jq -r ".schedule.$(date +%A).[] | [.time, .title] | join(\" \")" diff --git a/bin/extra/trl b/bin/extra/trl index bd4c2c5..55d65ee 100755 --- a/bin/extra/trl +++ b/bin/extra/trl @@ -41,10 +41,10 @@ then  fi  [ "$word" ] || exit 1 -primary="$(languages | fzf)" +primary="$(languages | fzf --prompt="from:")"  [ "$primary" ] || exit 1 -secondary="$(languages | fzf)" +secondary="$(languages | fzf --prompt="to:")"  [ "$secondary" ] || exit 1  curl -s "https://context.reverso.net/translation/$primary-$secondary/$word" \ diff --git a/bin/extra/trmv b/bin/extra/trmv new file mode 100755 index 0000000..22d9e48 --- /dev/null +++ b/bin/extra/trmv @@ -0,0 +1,13 @@ +#!/bin/sh + +trr() { transmission-remote 192.168.178.79 "$@"; } + +id="$(trr -t all -l | tail -n +2 | head -n -1 | fzf | awk '{print $1}')" +[ "$id" ] || exit 1 +name="$(trr -t "$id" -i | grep '^\s*Name:' | cut -f 4- -d ' ')" +location="$(trr -t "$id" -i | grep '^\s*Location:' | cut -f 4- -d ' ')" + +>&2 printf '#%s\n' "$id" +>&2 printf "old name: %s\n" "$name" +>&2 printf 'new name: ' +trr -t "$id" --path "$name" --rename "$(head -n 1)" diff --git a/bin/extra/ytplay b/bin/extra/ytplay index 66204c4..5243364 100755 --- a/bin/extra/ytplay +++ b/bin/extra/ytplay @@ -1,4 +1,4 @@  #!/bin/sh  url="$(ytlink)" -notify-send "playing: $url" & +herbe "playing: $url" &  yt-dlp -o - "$url" | mpv - diff --git a/bin/guiscripts/mega.sh b/bin/guiscripts/mega.sh new file mode 100644 index 0000000..cafca0a --- /dev/null +++ b/bin/guiscripts/mega.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +URL="" + +if [[ $1 =~ ^https?:\/\/mega(\.co)?\.nz ]]; then +	URL="$1" +fi + +if [[ ! $URL ]]; then +	echo "Usage: ${0##*/} url" >&2 +	exit 1 +fi + +CURL="curl -Y 1 -y 10" + +missing=false +for cmd in openssl; do +	if [[ ! $(command -v "$cmd" 2>&1) ]]; then +		missing=true +		echo "${0##*/}: $cmd: command not found" >&2 +	fi +done +if $missing; then +	exit 1 +fi + +if [[ $URL =~ .*/file/[^#]*#[^#]* ]]; then +	id="${URL#*file/}"; id="${id%%#*}" +	key="${URL##*file/}"; key="${key##*#}" +else +	id="${URL#*!}"; id="${id%%!*}" +	key="${URL##*!}" +fi + +raw_hex=$(echo "${key}=" | tr '\-_' '+/' | tr -d ',' | base64 -d -i 2>/dev/null | od -v -An -t x1 | tr -d '\n ') +hex=$(printf "%016x" \ +	$(( 0x${raw_hex:0:16} ^ 0x${raw_hex:32:16} )) \ +	$(( 0x${raw_hex:16:16} ^ 0x${raw_hex:48:16} )) +) + +json=$($CURL -s -H 'Content-Type: application/json' -d '[{"a":"g", "g":"1", "p":"'"$id"'"}]' 'https://g.api.mega.co.nz/cs?id=&ak=') || exit 1; json="${json#"[{"}"; json="${json%"}]"}" +file_url="${json##*'"g":'}"; file_url="${file_url%%,*}"; file_url="${file_url//'"'/}" + +json=$($CURL -s -H 'Content-Type: application/json' -d '[{"a":"g", "p":"'"$id"'"}]' 'https://g.api.mega.co.nz/cs?id=&ak=') || exit 1 +at="${json##*'"at":'}"; at="${at%%,*}"; at="${at//'"'/}" + +json=$(echo "${at}==" | tr '\-_' '+/' | tr -d ',' | openssl enc -a -A -d -aes-128-cbc -K "$hex" -iv "00000000000000000000000000000000" -nopad | tr -d '\0'); json="${json#"MEGA{"}"; json="${json%"}"}" +file_name="${json##*'"n":'}" +if [[ $file_name == *,* ]]; then +	file_name="${file_name%%,*}" +fi +file_name="${file_name//'"'/}" + +aria2c -x 15 -o "$file_name" "$file_url" +cat "$file_name" | openssl enc -d -aes-128-ctr -K "$hex" -iv "${raw_hex:32:16}0000000000000000" > "temp.new" +mv -f temp.new "$file_name" + +echo "$file_url" +echo "$file_name" +echo "$hex" +echo "${raw_hex:32:16}0000000000000000" +sleep 5 +echo "Downloading... (press Ctrl + C to Cancel)" + +aria2c -x 16 -s 16 -o "$file_name" "$file_url" +cat "$file_name" | openssl enc -d -aes-128-ctr -K "$hex" -iv "${raw_hex:32:16}0000000000000000" > "temp.new" +mv -f temp.new "$file_name" diff --git a/bin/guiscripts/osurf b/bin/guiscripts/osurf new file mode 100755 index 0000000..6923848 --- /dev/null +++ b/bin/guiscripts/osurf @@ -0,0 +1,2 @@ +#!/bin/sh +tabbed -c -dn tabbed-surf -r 2 surf -e '' "$1" diff --git a/bin/guiscripts/osurf-fill b/bin/guiscripts/osurf-fill new file mode 100755 index 0000000..43af807 --- /dev/null +++ b/bin/guiscripts/osurf-fill @@ -0,0 +1,127 @@ +#!/bin/sh + +# Fills a password for a given website  +# original script by Avalon Williams (avalonwilliams@protonmail.com) +# This version uses the window id to know the url of the surf window +# and to know which fifo it must use.  Then it injects javascript code. +# that will fill the forms with the credentials + +# dependencies: +# - surf fifo patch (http://surf.suckless.org/patches/fifo/) +# - xprop +# - 'pass' with password store in form dir/url/pass.gpg + +# $1: winid + +fifodir="$HOME/.config/surf/fifo"  +if [ -z "${winid:=$1}" ] +then +    winid="$(osurfls | dmenu -c -F -i | cut -f1 -d' ')" +fi +[ "$winid" ] || exit 1 +fifo="$fifodir/$winid" +[ -p "$fifo" ] || exit 2 + +# Get only domain name + top-level domain +url="$(xprop -id "$winid" _SURF_URI | +	 cut -f 2 -d'"' | +	 sed 's,^.*://\([^/]*\)/.*,\1,' | +	 sed -r -e 's/^([^.]+)\.([^.]+)\.([^.]+)$/\2.\3/')" +[ "$url" ] || exit 3 +>&2 printf 'url: %s\n' "$url" + +# get pass with url and ask if multiple are found +pass="$({ find $PASSWORD_STORE_DIR/websites/ -type f -name '*.gpg' | +	 grep "$url/" || echo; } | head -n 1 | +	 sed "s,$PASSWORD_STORE_DIR/,,;s/\.gpg$//" | +	 dmenu -c)" +# if dmenu was stopped, exit +[ $? -gt 0 ] && exit 4 + +# if no password was found, search through password store manually with dmenu +if [ -z "$pass" ] +then + 	store="${PASSWORD_STORE_DIR:-$HOME/.password-store}" + 	while [ -d "$store/$file" ] +	do +		choice="$(find "$store/$file" \ +			-maxdepth 1 -mindepth 1 \ +			-not -name '.*' -type d -printf "%y\t%f\n" -o \ +			-not -name '.*' -not -type d -printf "%y\t%f\n" | +			sort -k1 -k2 | +			cut -f 2 | sed 's/\.gpg$//' | +			dmenu -c)" +		[ "$choice" ] || exit 1 +		[ -z "$file" ] && file="$choice" || file="$file/$choice" +	done +	pass="$file" +fi +>&2 printf 'pass: %s\n' "$pass" +herbe "filling ${pass#websites/}" & + +# Get password and username in variables with only one call to 'pass' +# escape single quotes +eval "$(pass show "$pass" | +	 sed -n "1s/'/'\\\\''/g;1s/.*/password='&'/p;s/^login: \?\(.\+\)/username='\1'/p")" +printf '%s : %s\n' "$username" "$password" + +# Escape quotes and backslashes for javascript +javascript_escape() { +    printf '%s' "$1" | sed -s 's,['\''"\\\\],\\\\&,g' +} + +js() { +cat <<EOF +    function isVisible(elem) { +        var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null); +        if (style.getPropertyValue("visibility") !== "visible" || +            style.getPropertyValue("display") === "none" || +            style.getPropertyValue("opacity") === "0") { +            return false; +        } +        return elem.offsetWidth > 0 && elem.offsetHeight > 0; +    }; +    function hasPasswordField(form) { +        var inputs = form.getElementsByTagName("input"); +        for (var j = 0; j < inputs.length; j++) { +            var input = inputs[j]; +            if (input.type == "password" || input.autocomplete == "password" || input.name == "password") { +                return true; +            } +        } +        return false; +    }; +    function loadData2Form (form) { +        var inputs = form.getElementsByTagName("input"); +        for (var j = 0; j < inputs.length; j++) { +            var input = inputs[j]; +            if (isVisible(input) && (input.type == "text" || input.type == "email")) { +                input.focus(); +                input.value = "$(javascript_escape "$username")"; +                input.blur(); +                console.log("user: $(javascript_escape "$username")") +            } +            if (input.type == "password" || input.name == "password" || input.autocomplete == "password" || input.id == "password" ) { +                input.focus(); +                input.value = "$(javascript_escape "$password")"; +                input.blur(); +                console.log("password: $(javascript_escape "$password")") +            } +            console.log(input) +        } +    }; +    var forms = document.getElementsByTagName("form"); +    for (i = 0; i < forms.length; i++) { +        if (hasPasswordField(forms[i])) { +            loadData2Form(forms[i]); +	    // forms[i].submit(); +        } +    } +EOF +} + +printjs() { +    js | sed 's,//.*$,,' | tr '\n' ' ' +} + +echo "inject $(printjs)" >> "$fifo" diff --git a/bin/guiscripts/osurf-txt b/bin/guiscripts/osurf-txt new file mode 100755 index 0000000..9a1d4f4 --- /dev/null +++ b/bin/guiscripts/osurf-txt @@ -0,0 +1,18 @@ +#!/bin/sh + +# open a link from a txt file in surf + +# dependencies: surf, osurf, dmenu + +winid="$1" +>&2 printf 'winid: %s\n' "$winid" +tabs="$HOME/dl/txtabs" + +f="$(find "$tabs" -type f -printf '%f\n' | dmenu -c)" +[ "$f" ] || exit 1 +f="$tabs"/"$f" +>&2 printf 'f: %s\n' "$f" + +url="$(dmenu -c < "$f")" + +printf 'loaduri %s' "$url" > $HOME/.config/surf/fifo/$winid diff --git a/bin/guiscripts/osurfls b/bin/guiscripts/osurfls new file mode 100755 index 0000000..0abdd35 --- /dev/null +++ b/bin/guiscripts/osurfls @@ -0,0 +1,8 @@ +#!/bin/sh +find "$HOME/.config/surf/fifo" -type p -printf '%f\n' | +while read -r winid +do +	title="$(xprop -id "$winid" 2> /dev/null | awk -F'"' '/^_NET_WM_NAME/ {print $2}')" +	[ "$title" ] || continue +	printf '%s %s\n' "$winid" "$title" +done 
\ No newline at end of file diff --git a/bin/guiscripts/osurftabs b/bin/guiscripts/osurftabs new file mode 100755 index 0000000..d41424b --- /dev/null +++ b/bin/guiscripts/osurftabs @@ -0,0 +1,12 @@ +#!/bin/sh + +# list surf tabbed windows  + +# dependencies: lsw, dmenu, xprop +# expects the tabbed windows to be named 'tabbed-surf' +lsw | cut -f1 -d' ' | +	while read -r winid  +	do  +		[ "tabbed-surf" = "$(xprop -id "$winid" WM_CLASS | cut -f2 -d'"')" ] && +			 printf '%s %s\n' "$winid"  "$(xprop -id "$winid" WM_NAME | cut -f2 -d'"')" +	done diff --git a/bin/guiscripts/osurftxts b/bin/guiscripts/osurftxts new file mode 100755 index 0000000..ef60166 --- /dev/null +++ b/bin/guiscripts/osurftxts @@ -0,0 +1,22 @@ +#!/bin/sh + +# open all links in txt file into one tabbed surf + +# dependencies: surf, osurf, dmenu + +# $1: file path for non interactive use +if [ -z "$1" ] +then  +	d="$HOME/dl/txtabs" +	f="$(find "$d" -type f -printf '%f\n' | dmenu)" +	[ "$f" ] || exit 1 +	f="$d"/"$f" +else +	[ -f "$1" ] || exit 1 +	f="$1" +fi + +winid="$(osurf "$(head -n 1 "$f")")" +tail -n +2 "$f" | while read -r url; +	do surf -e "$winid" "$url" & +	done diff --git a/bin/guiscripts/record b/bin/guiscripts/record new file mode 100755 index 0000000..df4b6e6 --- /dev/null +++ b/bin/guiscripts/record @@ -0,0 +1,100 @@ +#!/bin/sh + +# record - record an area of the screen + +lock="/tmp/record.lock" + +# dependencies: ffmpeg, hacksaw (part), xwininfo & lsw (window), xdotool (active)  +# optional: +# 	- hacksaw: part +# 	- xwininfo, lsw, commander: window +# 	- xdotool: active +# 	- xdg-user-dir + +audio= + +# $1: width +# $2: height +# $3: x +# $4: y +# $5: output dir +# $6: output name +record_cmd() +{ +	if [ -f "$lock" ]  +	then +		>&2 printf 'already recording, please stop recording first\n' +		exit 1 +	else +		touch "$lock" +	fi + +	herbe "started recording." & +	w=$(($3 + $3 % 2)) +	h=$(($4 + $4 % 2)) +	ffmpeg $audio        \ +	    -v 16            \ +	    -r 30            \ +	    -f x11grab       \ +	    -s "${w}x${h}"   \ +	    -i ":0.0+$1,$2"  \ +	    -preset slow     \ +	    -c:v h264        \ +	    -pix_fmt yuv420p \ +	    -crf 20          \ +	    "$5/$6.mp4" +	printf '%s\n' "$5/$6.mp4" +	rm -f "$lock" +	herbe "stopped recording." &  +} + +if [ -d "$1" ] +then +	dir="$1"  +	shift +else  +	dir="$(which xdg-user-dir > /dev/null 2>&1 && xdg-user-dir VIDEOS)" +	[ "$dir" ] && dir="$dir/records" || dir="$HOME/vids/records" +fi +mkdir -p "$dir" + +if [ "$1" = "-a" ] +then +	audio="-f pulse -ac 2 -i default" +	shift +fi + +if [ "$1" = "-l" ] +then +	find vids/records/ -type f | sort | tail -n 1 +	exit +fi + +current=$(date +%F_%H-%M-%S) + +[ "$1" ] && option="$1" || option="$(printf 'active\nwindow\npart\nstop\nfull\naudio' | commander -c)" +case "$option" in +    active) +        record_cmd $(xwininfo -id "$(xdotool getactivewindow)" | +            sed -e '/Absolute\|Width:\|Height:/!d;s/.*:\s*//' | tr '\n' ' ') $dir $current +        ;; + +    window) +    	winid="$(lsw | commander -cxl | cut -d' ' -f1)" +    	[ "$winid" ] || exit 1 +    	values="$(xwininfo -id "$winid" | sed -e '/Absolute\|Width:\|Height:/!d;s/.*:\s*//' | tr '\n' ' ')" +	[ "$values" ] || exit 1 +        record_cmd $values $dir $current +        ;; +         +    part)  +        hacksaw | { +            IFS=+x read -r w h x y +            record_cmd $w $h $x $y $dir $current +        } +        ;; +    stop) kill "$(pgrep ffmpeg | xargs ps | grep 'x11grab' | awk '{print $1}')"; rm -f "$lock" ;; +    full) record_cmd 0 0 1920 1080 $dir $current ;; +    audio) $0 -a; exit ;; +    help|*) >&2 printf 'record [dir] (active|window|part|stop|full)\n' ;; +esac diff --git a/bin/guiscripts/setbg b/bin/guiscripts/setbg new file mode 100755 index 0000000..a4109ab --- /dev/null +++ b/bin/guiscripts/setbg @@ -0,0 +1,5 @@ +#!/bin/sh +cd "$HOME/pics/wallpapers" || exit 1 +bg="$(find . -type f -printf '%f\n' | sed 's@^\./@@' | dmenu -c -x)" +[ "$bg" ] || exit 1 +feh --no-fehbg --bg-scale "$bg" diff --git a/bin/guiscripts/startw b/bin/guiscripts/startw index 0383f48..dbf4450 100755 --- a/bin/guiscripts/startw +++ b/bin/guiscripts/startw @@ -1,8 +1,4 @@  #!/bin/sh -  eval "$(keychain --dir "$XDG_CONFIG_HOME/keychain" --eval --quiet --agents gpg,ssh)"  eval "$(keychain --dir "$XDG_CONFIG_HOME/keychain" --eval --quiet --agents gpg 3A626DD20A32EB2E5DD9CE71CFD9ABC97158CD5D 2> /dev/null)" - -(cd ~/.config/waybar/ && ln -sf hyprland.jsonc config.jsonc) -  Hyprland diff --git a/bin/guiscripts/yt b/bin/guiscripts/yt new file mode 100755 index 0000000..72f6e92 --- /dev/null +++ b/bin/guiscripts/yt @@ -0,0 +1,4 @@ +#!/bin/sh +link="$(ytfzf -D -I l)" +[ "$link" ] || exit 1 +yt-dlp $@ -o - "$link" | mpv - diff --git a/bin/menuscripts/keyadd b/bin/menuscripts/keyadd index 12519ec..4e7949f 100755 --- a/bin/menuscripts/keyadd +++ b/bin/menuscripts/keyadd @@ -2,7 +2,7 @@  log()  { -	notify-send -t 1000 "keyadd" "$1" +	notify-send -t 1000 "keyadd" "$1" &  	>&2 printf '%s\n' "$1"  } diff --git a/bin/menuscripts/mpass b/bin/menuscripts/mpass index 7348321..f513b16 100755 --- a/bin/menuscripts/mpass +++ b/bin/menuscripts/mpass @@ -14,7 +14,7 @@ list_pswds()  while [ -d "$store/$file" ]  do -	choice="$(list_pswds "$store/$file" | dmenu -c -g 4 -l 4)" +	choice="$(list_pswds "$store/$file" | commander -c)"  	[ "$choice" ] || exit 1  	[ -z "$file" ] && file="$choice" || file="$file/$choice"  done diff --git a/bin/menuscripts/mpass-otp b/bin/menuscripts/mpass-otp index 52d1341..2be6186 100755 --- a/bin/menuscripts/mpass-otp +++ b/bin/menuscripts/mpass-otp @@ -1,7 +1,7 @@  #!/bin/sh  pass="$(find "$PASSWORD_STORE_DIR"/keys/otp -iname "*.gpg" |      sed "/^\./d;s#^$PASSWORD_STORE_DIR/keys/otp/##;s/\.gpg$//" | -    commander -c)" +    dmenu -c)"  [ "$pass" ] || exit 1  pass otp -c keys/otp/"$pass"  notify-send -t 1000 "mpass" "copied $pass" diff --git a/bin/menuscripts/tsh b/bin/menuscripts/tsh index aac27ee..0c57ee0 100755 --- a/bin/menuscripts/tsh +++ b/bin/menuscripts/tsh @@ -5,8 +5,8 @@ PROG="$(basename "$0")"  # copy command and deps variable  deps="pup curl $MENUCMD" -LIBPFX=/home/aluc/.local/share/tsh -module='1337x.sh' # default module +MODULES_PATH=$HOME/.local/share/tsh +module='nyaa.sh' # default module  # Files  export tmp="/tmp/$PROG" @@ -83,7 +83,7 @@ cleanup ()  	done  } -list_modules () { find -L "$LIBPFX" -type f -printf "%f\n"; } +list_modules () { find -L "$MODULES_PATH" -type f -printf "%f\n"; }  # get a query from user based on MENUCMD  get_query () @@ -121,14 +121,6 @@ show_files()  	rm -f "$tmp"/.torrent  } -# Select a type after having displayed them with 'show_types' -select_type() -{ -	for type in $categories -	do printf "%s\n" "$type" -	done | fzf -} -  trap "exit 1" INT  trap "cleanup" EXIT @@ -179,7 +171,7 @@ then  	# Get results  	rm -f "$results" "$links"  	# shellcheck source=/usr/local/lib/$PROG/nyaa.sh disable=SC1091 -	. "$LIBPFX/$module" +	. "$MODULES_PATH/$module"  	[ -f "$results" ] || die "No results."  	# Save which module was used @@ -190,7 +182,7 @@ fi  # acquire get_magnet function  # shellcheck source=/usr/local/lib/$PROG/nyaa.sh disable=SC1091 -getfunctions=1 . "$LIBPFX/$module" +getfunctions=1 . "$MODULES_PATH/$module"  # select result from "$results"  for choice in $(select_result | xargs) @@ -205,7 +197,8 @@ do      if [ "$noaskdownload" ] || confirm 'download?'      then -    	    [ "${category:-$(select_type)}" ] || exit 1 +    	    [ "$category" ] || category="$(printf '%s' "$categories" | tr ' ' '\n' | fzf)" +    	    [ "$category" ] || exit 2  	    transmission-remote debuc.com -a "$magnet" -w "/downloads/$category"      elif confirm "copy?"      then diff --git a/config/X/x11/xinitrc b/config/X/x11/xinitrc index bc1824a..8b3cf2b 100755 --- a/config/X/x11/xinitrc +++ b/config/X/x11/xinitrc @@ -22,7 +22,7 @@ export MENUCMD="dmenu"  export IMAGE="feh"  xcompmgr &  feh --no-fehbg --bg-scale ~/pics/wallpaper -setxkbmap us -option ctrl:swapcaps,altwin:menu_win -variant colemak +setxkbmap colemak -option ctrl:swapcaps,altwin:menu_win  # xautolock -locker slock &  gammastep -m randr &  # dunst & diff --git a/config/common/mpd/mpd.conf b/config/common/mpd/mpd.conf index 095b345..889b458 100644 --- a/config/common/mpd/mpd.conf +++ b/config/common/mpd/mpd.conf @@ -1,4 +1,4 @@ -music_directory      "/media/manthe/music" +music_directory      "~/music"  playlist_directory   "~/.config/mpd/playlists"  db_file              "~/.config/mpd/database"  pid_file             "~/.config/mpd/pid" diff --git a/config/common/mpv/mpv.conf b/config/common/mpv/mpv.conf index 1fd96da..2e7318e 100755 --- a/config/common/mpv/mpv.conf +++ b/config/common/mpv/mpv.conf @@ -17,10 +17,11 @@ ytdl-raw-options=extractor-args="youtube:player-client=android"  # Default demuxer is 150/75 MB, note that this uses RAM so set a reasonable amount.  # 150MB, Max pre-load for network streams (1 MiB = 1048576 Bytes). -demuxer-max-bytes=150000000  +demuxer-max-bytes=150MiB +demuxer-readahead-secs=20  # 75MB, Max loaded video kept after playback. -demuxer-max-back-bytes=75000000  +demuxer-max-back-bytes=75MiB  # Force stream to be seekable even if disabled.  force-seekable=yes  diff --git a/config/common/mpv/scripts/mpv-skipsilence b/config/common/mpv/scripts/mpv-skipsilence new file mode 160000 +Subproject 2d6fd04dca3c70edf816e9af6fc30b302eb1c7a diff --git a/config/common/newsraft/feeds b/config/common/newsraft/feeds index 77e5420..683490c 100644 --- a/config/common/newsraft/feeds +++ b/config/common/newsraft/feeds @@ -25,8 +25,12 @@ https://arthurmelton.com/blogs.rss "Arthur Melton's blog'"  @ Linux  https://www.youtube.com/feeds/videos.xml?channel_id=UC-V8FVQCUpRRUPNClviki3w "Luke Smith" -https://youtube.com/feeds/video.xml?channel_id=UCngn7SVujlvskHRvRKc1cTw "Bugswriter" -https://youtube.com/feeds/video.xml?channel_id=UCVls1GmFKf6WlTraIb_IaJg "DistroTube" +https://www.youtube.com/feeds/videos.xml?channel_id=UCngn7SVujlvskHRvRKc1cTw "Bugswriter" +https://www.youtube.com/feeds/videos.xml?channel_id=UCVls1GmFKf6WlTraIb_IaJg "DistroTube" +https://www.youtube.com/feeds/videos.xml?channel_id=UCuGS5mN1_CpPzuOUAu2LluA "NixHero" +https://www.youtube.com/feeds/videos.xml?channel_id=UCWQaM7SpSECp9FELz-cHzuQ "Dreams of Code" +https://www.youtube.com/feeds/videos.xml?channel_id=UCCuoqzrsHlwv1YyPKLuMDUQ "Jonathan Blow" +  @ Entertainment  https://www.youtube.com/feeds/videos.xml?channel_id=UCi8C7TNs2ohrc6hnRQ5Sn2w "Programmers are also human" @@ -37,5 +41,5 @@ https://www.youtube.com/feeds/videos.xml?channel_id=UCVk4b-svNJoeytrrlOixebQ "Th  https://www.youtube.com/feeds/videos.xml?channel_id=UCd3dNckv1Za2coSaHGHl5aA "TJ DeVries"  @ News -https://rss.rtbf.be/article/rss/highlight_rtbf_info.xml?source=internal -https://news.ycombinator.com/rss +https://rss.rtbf.be/article/rss/highlight_rtbf_info.xml?source=internal "RTBF" +https://news.ycombinator.com/rss "HackerNews" diff --git a/config/common/tmux/tmux.conf b/config/common/tmux/tmux.conf index 9300c97..8cf10e5 100755 --- a/config/common/tmux/tmux.conf +++ b/config/common/tmux/tmux.conf @@ -120,7 +120,7 @@ set -g status on  #+--- Layout ---+  set -g set-titles-string "[#S: #W] #T"  set -g set-titles on -set -g status-position top +set -g status-position bottom  set -g window-status-current-style "underscore"  set -g status-justify left  set -g status-left-length 16 diff --git a/config/essentials/shell/aliases.sh b/config/essentials/shell/aliases.sh index a8b9036..ddc605a 100644 --- a/config/essentials/shell/aliases.sh +++ b/config/essentials/shell/aliases.sh @@ -3,7 +3,8 @@  # The most important one  alias vi='vis' -alias cd='z' +which z > /dev/null 2>&1 && +    alias cd='z'  # Zsh specific aliases  if [ $SHELL = "/bin/zsh" ] @@ -53,7 +54,8 @@ alias pf='profanity'  alias f='fg' -alias gurk='pgrep gurk > /dev/null && printf "Already Running.\n" || gurk' +which gurk > /dev/null 2>&1 && +    alias gurk='pgrep gurk > /dev/null && printf "Already Running.\n" || gurk'  alias arduino-cli='arduino-cli --config-file $XDG_CONFIG_HOME/arduino15/arduino-cli.yaml' @@ -162,9 +164,6 @@ alias wtip='wt ip -c -brief addr'  alias fusephone='sshfs myphone: /media/phone'  alias ttyper='ttyper -l english1000' -alias wgup='doas wg-quick up wg0' -alias wgdown='doas wg-quick down wg0' -  # NPM  alias npi="npm init --yes" @@ -339,6 +338,7 @@ alias ddeps='pactree -r -d 1'  alias update-mirrors='reflector -p https | rankmirrors -n 10 -p -w - | doas tee /etc/pacman.d/mirrorlist'  alias tmpd='cd $(mktemp -d)' +alias tmpf='$EDITOR $(mktemp)'  alias brs='$BROWSER'  which bat > /dev/null 2>&1 &&      alias cat="bat -p" diff --git a/config/essentials/shell/functions.sh b/config/essentials/shell/functions.sh index b69b775..27eb33e 100644 --- a/config/essentials/shell/functions.sh +++ b/config/essentials/shell/functions.sh @@ -241,6 +241,7 @@ pacsize()  mime-default ()  { +	mime=      [ "${mime:=$1}" ] ||          mime="$(find /usr/share/applications/ -iname '*.desktop' -printf '%f\n' |              sed 's/\.desktop$//' | @@ -305,7 +306,7 @@ gdown () {  }  # toggle wireguard vpn on $1 -> interface -wgtoggle() {  +wgt() {   	d="${1:-wg0}"  	ip -br a | awk '{print $1}' | grep "$d" > /dev/null &&          doas wg-quick down "$d" || @@ -317,17 +318,10 @@ serve() {      if [ "$1" ]      then          logn "Serving $1" -        docker container run \ -            --rm \ -            --volume "$(readlink -f "$1")":/data \ -            --publish 80:5000 sigoden/dufs /data +        dufs "$1"      else -          logn "Receiving files.." -        docker container run \ -            --rm \ -            --volume /tmp/data:/data \ -            --publish 80:5000 sigoden/dufs /data --allow-upload +        dufs /tmp/data --alow-upload      fi  } @@ -364,3 +358,9 @@ ssh_port()      ssh -f -N -L 0.0.0.0:"$3":localhost:"$1" "$2"      >&2 printf "Forwarded port '%s' on '%s' to '%s'.\n" "$1" "$2" "$3"  } +ffconcat () { +	tmp=$(mktemp -p . ffconcat.XXXXX)  +	sed 's/.*/file &/' > "$tmp" +	ffmpeg -y -f concat -safe 0 -i $tmp -c copy "$1" +	rm $tmp +} diff --git a/config/essentials/vis/Makefile b/config/essentials/vis/Makefile new file mode 100644 index 0000000..f2d386b --- /dev/null +++ b/config/essentials/vis/Makefile @@ -0,0 +1,6 @@ +.PHONY: check  + +LUA_FILES := $(shell find . -type f -name "*.lua") + +check: +	luacheck --no-color --globals=vis -- $(LUA_FILES) diff --git a/config/essentials/vis/backup.lua b/config/essentials/vis/backup.lua new file mode 100644 index 0000000..f48895f --- /dev/null +++ b/config/essentials/vis/backup.lua @@ -0,0 +1,47 @@ +local backup = {} + +-- Return the backup path concatenated with the filename, replace / with % +backup.entire_path_with_double_percentage_signs = +    function(backup_dir, path) +        return backup_dir .. "/" .. string.gsub(path, "/", "%%") +    end + +-- Return the backup path concatenated with the filename, replace / with % +-- and append the current time using time_format +backup.entire_path_with_double_percentage_signs_and_timestamp = function( +    backup_dir, path) +    return backup_dir .. "/" .. os.date(backup.time_format) .. +               string.gsub(path, "/", "%%") +end + +-- Before saving the file, copy the current contents of the file to a backup file +vis.events.subscribe(vis.events.FILE_SAVE_PRE, function(file, path) +    if file.size > backup.byte_limit then return end + +    -- E.g. when editing stdin as an interactive filter +    -- `vis -` +    if path == nil then return end + +    local backup_path = backup.get_fname(backup.directory, path) + +    local backup_file = io.open(backup_path, "w") +    local current_file = io.open(path) +    if backup_file == nil or current_file == nil then return end + +    for line in current_file:lines() do backup_file:write(line .. "\n") end + +    backup_file:close() +end) + +-- Set defaults +backup.directory = os.getenv("XDG_DATA_HOME") .. "/Trash/vis-backups" + +backup.get_fname = backup.entire_path_with_double_percentage_signs + +backup.time_format = "%H-%M-" + +-- Do not make backups if the file is greater than this +-- 1MB by default +backup.byte_limit = 1000000 + +return backup diff --git a/config/essentials/vis/build.lua b/config/essentials/vis/build.lua new file mode 100644 index 0000000..8a062cd --- /dev/null +++ b/config/essentials/vis/build.lua @@ -0,0 +1,66 @@ +-- Copyright (c) 2024 Florian Fischer. All rights reserved. +-- +-- vis-build is free software: you can redistribute it and/or modify it under +-- the terms of the GNU General Public License as published by the Free Software +-- Foundation, either version 3 of the License, or (at your option) any later +-- version. +-- +-- vis-build is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- vis-build found in the LICENSE file. +-- If not, see <https://www.gnu.org/licenses/>. +local M = {} +M.get_default_build_cmd = function() return 'make' end + +local build_id = 0 +local builds = {} + +M.new_build = function(cmd) +    build_id = build_id + 1 +    local build_name = 'build' .. build_id +    local build_fd = vis:communicate(build_name, cmd) +    local build = {fd = build_fd, out = '', err = '', cmd = cmd} +    builds[build_name] = build +end + +vis.events.subscribe(vis.events.PROCESS_RESPONSE, +                     function(name, event, code, msg) +    local build = builds[name] +    if not build then return end + +    if event == 'EXIT' or event == 'SIGNAL' then +        if code ~= 0 then +            vis:message('build: ' .. name .. ' cmd: ' .. build.cmd) +            if event == 'EXIT' then +                vis:message('failed with: ' .. code) +            else +                vis:message('got signal: ' .. code) +            end +            vis:message('stdout:\n' .. build.out) +            vis:message('stderr:\n' .. build.err) +        else +            vis:message(name .. ':\n' .. build.out) +        end +        builds[name] = nil +    end + +    if event == 'STDOUT' then +        build.out = build.out .. msg +    elseif event == 'STDERR' then +        build.err = build.err .. msg +    end +end) + +vis:command_register('build', function(argv) +    M.new_build(argv[1] or M.get_default_build_cmd()) +end, 'Asynchronously build the current file or project') + +vis:map(vis.modes.NORMAL, '<M-b>', function() +    vis:command('build') +    return 0 +end, 'Asynchronously build the current file or project') + +return M diff --git a/config/essentials/vis/commentary.lua b/config/essentials/vis/commentary.lua new file mode 100644 index 0000000..26d06b5 --- /dev/null +++ b/config/essentials/vis/commentary.lua @@ -0,0 +1,251 @@ +-- +-- vis-commentary +-- +local comment_string = { +    actionscript = '//', +    ada = '--', +    ansi_c = '/*|*/', +    antlr = '//', +    apdl = '!', +    apl = '#', +    applescript = '--', +    asp = '\'', +    autoit = ';', +    awk = '#', +    b_lang = '//', +    bash = '#', +    batch = ':', +    bibtex = '%', +    boo = '#', +    chuck = '//', +    cmake = '#', +    coffeescript = '#', +    context = '%', +    cpp = '//', +    crystal = '#', +    csharp = '//', +    css = '/*|*/', +    cuda = '//', +    dart = '//', +    desktop = '#', +    django = '{#|#}', +    dmd = '//', +    dockerfile = '#', +    dot = '//', +    eiffel = '--', +    elixir = '#', +    erlang = '%', +    faust = '//', +    fennel = ';;', +    fish = '#', +    forth = '|\\', +    fortran = '!', +    fsharp = '//', +    gap = '#', +    gettext = '#', +    gherkin = '#', +    glsl = '//', +    gnuplot = '#', +    go = '//', +    groovy = '//', +    gtkrc = '#', +    haskell = '--', +    html = '<!--|-->', +    icon = '#', +    idl = '//', +    inform = '!', +    ini = '#', +    Io = '#', +    java = '//', +    javascript = '//', +    json = '/*|*/', +    jsp = '//', +    latex = '%', +    ledger = '#', +    less = '//', +    lilypond = '%', +    lisp = ';', +    logtalk = '%', +    lua = '--', +    makefile = '#', +    markdown = '<!--|-->', +    matlab = '#', +    moonscript = '--', +    myrddin = '//', +    nemerle = '//', +    nsis = '#', +    objective_c = '//', +    pascal = '//', +    perl = '#', +    php = '//', +    pico8 = '//', +    pike = '//', +    pkgbuild = '#', +    prolog = '%', +    props = '#', +    protobuf = '//', +    ps = '%', +    pure = '//', +    python = '#', +    rails = '#', +    rc = '#', +    rebol = ';', +    rest = '.. ', +    rexx = '--', +    rhtml = '<!--|-->', +    rstats = '#', +    ruby = '#', +    rust = '//', +    sass = '//', +    scala = '//', +    scheme = ';', +    smalltalk = '"|"', +    sml = '(*)', +    snobol4 = '#', +    sql = '#', +    tcl = '#', +    tex = '%', +    text = '', +    toml = '#', +    vala = '//', +    vb = '\'', +    vbscript = '\'', +    verilog = '//', +    vhdl = '--', +    wsf = '<!--|-->', +    xml = '<!--|-->', +    yaml = '#', +    zig = '//', +    nim = '#', +    julia = '#', +    rpmspec = '#' +} + +-- escape all magic characters with a '%' +local function esc(str) +    if not str then return "" end +    return (str:gsub('%%', '%%%%'):gsub('^%^', '%%^'):gsub('%$$', '%%$'):gsub( +               '%(', '%%('):gsub('%)', '%%)'):gsub('%.', '%%.') +               :gsub('%[', '%%['):gsub('%]', '%%]'):gsub('%*', '%%*') +               :gsub('%+', '%%+'):gsub('%-', '%%-'):gsub('%?', '%%?')) +end + +-- escape '%' +local function pesc(str) +    if not str then return "" end +    return str:gsub('%%', '%%%%') +end + +local function rtrim(s) +    local n = #s +    while n > 0 and s:find("^%s", n) do n = n - 1 end +    return s:sub(1, n) +end + +local function comment_line(lines, lnum, prefix, suffix) +    if suffix ~= "" then suffix = " " .. suffix end +    lines[lnum] = string.gsub(lines[lnum], "(%s*)(.*)", +                              "%1" .. pesc(prefix) .. " %2" .. pesc(suffix)) +end + +local function uncomment_line(lines, lnum, prefix, suffix) +    local match_str = "^(%s*)" .. esc(prefix) .. "%s?(.*)" .. esc(suffix) +    local m = table.pack(lines[lnum]:match(match_str)) +    lines[lnum] = m[1] .. rtrim(m[2]) +end + +local function is_comment(line, prefix) +    return (line:match("^%s*(.+)"):sub(0, #prefix) == prefix) +end + +local function toggle_line_comment(lines, lnum, prefix, suffix) +    if not lines or not lines[lnum] then return end +    if not lines[lnum]:match("^%s*(.+)") then return end -- ignore empty lines +    if is_comment(lines[lnum], prefix) then +        uncomment_line(lines, lnum, prefix, suffix) +    else +        comment_line(lines, lnum, prefix, suffix) +    end +end + +-- if one line inside the block is not a comment, comment the block. +-- only uncomment, if every single line is comment. +local function block_comment(lines, a, b, prefix, suffix) +    local uncomment = true +    for i = a, b do +        if lines[i]:match("^%s*(.+)") and not is_comment(lines[i], prefix) then +            uncomment = false +        end +    end + +    if uncomment then +        for i = a, b do +            if lines[i]:match("^%s*(.+)") then +                uncomment_line(lines, i, prefix, suffix) +            end +        end +    else +        for i = a, b do +            if lines[i]:match("^%s*(.+)") then +                comment_line(lines, i, prefix, suffix) +            end +        end +    end +end + +vis:map(vis.modes.NORMAL, "gcc", function() +    local win = vis.win +    local lines = win.file.lines +    local comment = comment_string[win.syntax] +    if not comment then return end +    local prefix, suffix = comment:match('^([^|]+)|?([^|]*)$') +    if not prefix then return end + +    for sel in win:selections_iterator() do +        local lnum = sel.line +        local col = sel.col + +        toggle_line_comment(lines, lnum, prefix, suffix) +        sel:to(lnum, col) -- restore cursor position +    end + +    win:draw() +end, "Toggle comment on a the current line") + +local function visual_f(i) +    return function() +        local win = vis.win +        local lines = win.file.lines + +        local comment = comment_string[win.syntax] +        if not comment then return end + +        local prefix, suffix = comment:match('^([^|]+)|?([^|]*)$') +        if not prefix then return end + +        for sel in win:selections_iterator() do +            local r = sel.range +            local lnum = sel.line -- line number of cursor +            local col = sel.col -- column of cursor + +            if sel.anchored and r then +                sel.pos = r.start +                local a = sel.line +                sel.pos = r.finish +                local b = sel.line - i + +                block_comment(lines, a, b, prefix, suffix) + +                sel:to(lnum, col) -- restore cursor position +            end +        end + +        win:draw() +        vis.mode = vis.modes.NORMAL -- go to normal mode +    end +end + +vis:map(vis.modes.VISUAL_LINE, "gc", visual_f(1), +        "Toggle comment on the selected lines") +vis:map(vis.modes.VISUAL, "gc", visual_f(0), +        "Toggle comment on the selected lines") diff --git a/config/essentials/vis/complete-line.lua b/config/essentials/vis/complete-line.lua new file mode 100644 index 0000000..93728a3 --- /dev/null +++ b/config/essentials/vis/complete-line.lua @@ -0,0 +1,141 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +-- © 2020 Georgi Kirilov +local progname = ... + +local lpeg = require("lpeg") +local R, C, Cmt = lpeg.R, lpeg.C, lpeg.Cmt + +-- copied from vis.h +local VIS_MOVE_NOP = 64 + +local cont = R("\128\191") + +local charpattern = R("\0\127") + R("\194\223") * cont + R("\224\239") * cont * +                        cont + R("\240\244") * cont * cont * cont + +local function concat_keys(tbl) +    local keys = {} +    for k in pairs(tbl) do table.insert(keys, k) end +    return table.concat(keys, "\n"), #keys +end + +local function line_complete() +    local file = vis.win.file +    local sel = vis.win.selection +    local cur_line = file.lines[sel.line] +    local indent_patt = "^[ \t\v\f]+" +    local prefix = cur_line:sub(1, sel.col - 1):gsub(indent_patt, "") +    local candidates = {} +    for l in file:lines_iterator() do +        local unindented = l:gsub(indent_patt, "") +        local start, finish = unindented:find(prefix, 1, true) +        if start == 1 and finish < #unindented then +            candidates[unindented] = true +        end +    end +    local candidates_str, n = concat_keys(candidates) +    if n < 2 then +        if n == 1 then vis:insert(candidates_str:sub(#prefix + 1)) end +        return +    end +    -- XXX: with too many candidates this command will become longer that the shell can handle: +    local command = string.format("vis-menu -l %d <<'EOF'\n%s\nEOF\n", +                                  math.min(n, math.ceil(vis.win.height / 2)), +                                  candidates_str) +    local status, output = vis:pipe(nil, nil, command) +    if n > 0 and status == 0 then +        vis:insert(output:sub(#prefix + 1):gsub("\n$", "")) +    end +end + +local function selection_by_pos(pos) +    for s in vis.win:selections_iterator() do +        if s.pos == pos then return s end +    end +end + +local function charwidth(cells_so_far, char) +    if char == "\t" then +        local tw = vis.tabwidth or 8 +        local trail = cells_so_far % tw +        return tw - trail +    else +        return 1 +    end +end + +local function virtcol(line, col) +    local ncells = 0 +    local nchars = 0 +    local function upto(_, _, char) +        if nchars < col - 1 then +            ncells = ncells + charwidth(ncells, char) +            nchars = nchars + 1 +            return true +        end +    end +    (Cmt(C(charpattern), upto) ^ 0):match(line) +    return ncells + 1 +end + +local function neighbor(lines, ln, col, direction) +    local line = ln + direction > 0 and lines[ln + direction] +    if not line then return end +    local column = virtcol(lines[ln], col) +    local ncells = 0 +    local function upto(_, _, char) +        ncells = ncells + charwidth(ncells, char) +        return ncells < column +    end +    return +        (Cmt(C(charpattern), upto) ^ (-column + 1) / 0 * C(charpattern)):match( +            line) +end + +local function dup_neighbor(direction) +    return function(file, _, pos) +        local sel = selection_by_pos(pos) +        local char = neighbor(file.lines, sel.line, sel.col, direction) +        if not char then return pos end +        file:insert(pos, char) +        return pos + #char +    end +end + +local function dup_neighbor_feedkeys(direction) +    local sel = vis.win.selection +    local file = vis.win.file +    local char = neighbor(file.lines, sel.line, sel.col, direction) +    if not char then return end +    vis:feedkeys(char) +end + +local function operator(handler) +    local id = vis:operator_register(handler) +    return id >= 0 and function() +        vis:operator(id) +        vis:motion(VIS_MOVE_NOP) +    end +end + +vis.events.subscribe(vis.events.INIT, function() +    local function h(msg) return string.format("|@%s| %s", progname, msg) end + +    local function column_complete(direction) +        local binding = operator(dup_neighbor(direction)) +        return function() +            if #vis.win.selections == 1 then +                dup_neighbor_feedkeys(direction) +            else +                return binding() +            end +        end +    end + +    vis:map(vis.modes.INSERT, "<C-y>", column_complete(-1), +            h "Insert the character which is above the cursor") +    vis:map(vis.modes.INSERT, "<C-e>", column_complete(1), +            h "Insert the character which is below the cursor") +    vis:map(vis.modes.INSERT, "<C-x><C-l>", line_complete, +            h "Complete the current line") +end) diff --git a/config/essentials/vis/cursors.lua b/config/essentials/vis/cursors.lua new file mode 100644 index 0000000..5b3d43b --- /dev/null +++ b/config/essentials/vis/cursors.lua @@ -0,0 +1,105 @@ +local M = {} +local cursors = {} +local files = {} + +-- default maxsize +M.maxsize = 1000 + +-- get the default system cache directory +local function get_default_cache_path() +    local HOME = os.getenv('HOME') +    local XDG_CACHE_HOME = os.getenv('XDG_CACHE_HOME') +    local BASE = XDG_CACHE_HOME or HOME +    return BASE .. '/.vis-cursors' +end + +-- default save path +M.path = get_default_cache_path() + +local function read_files() + +    -- read file +    local file = io.open(M.path) +    if file == nil then return end + +    files = {} + +    -- read positions per file path +    for line in file:lines() do +        for path, pos in string.gmatch(line, '(.+)[,%s](%d+)') do +            cursors[path] = pos +            table.insert(files, path) +        end +    end + +    file:close() +end + +-- read cursors from file on init +local function on_init() read_files() end + +-- apply cursor pos on win open +local function on_win_open(win) + +    if win.file == nil or win.file.path == nil then return end + +    -- init cursor path if nil +    local pos = cursors[win.file.path] +    if pos == nil then +        cursors[win.file.path] = win.selection.pos +        return +    end + +    -- set current cursor +    win.selection.pos = tonumber(pos) + +    -- center view around cursor +    vis:feedkeys("zz") +end + +-- set cursor pos on close +local function on_win_close(win) + +    if win.file == nil or win.file.path == nil then return end + +    -- re-read files in case they've changed +    read_files() + +    -- remove old occurences of current path +    for i, path in ipairs(files) do +        if path == win.file.path then table.remove(files, i) end +    end + +    -- ignore files with cursor at the beginning +    if win.selection.pos == 0 then return end + +    -- insert current path to top of files +    table.insert(files, 1, win.file.path) + +    -- set cursor pos for current file path +    cursors[win.file.path] = win.selection.pos +end + +-- write cursors to file on quit +local function on_quit() + +    local file = io.open(M.path, 'w+') +    if file == nil then return end + +    -- buffer cursors string +    local buffer = {} +    for _, path in ipairs(files) do +        table.insert(buffer, string.format('%s,%d', path, cursors[path])) +        if M.maxsize and #buffer >= M.maxsize then break end +    end +    local output = table.concat(buffer, '\n') +    file:write(output) +    file:close() +end + +vis.events.subscribe(vis.events.INIT, on_init) +vis.events.subscribe(vis.events.WIN_OPEN, on_win_open) +vis.events.subscribe(vis.events.WIN_CLOSE, on_win_close) +vis.events.subscribe(vis.events.QUIT, on_quit) + +return M diff --git a/config/essentials/vis/format.lua b/config/essentials/vis/format.lua new file mode 100644 index 0000000..e39320e --- /dev/null +++ b/config/essentials/vis/format.lua @@ -0,0 +1,134 @@ +local global_options = { check_same = true } + +local function stdio_formatter(cmd, options) +	local function apply(win, range, pos) +		local size = win.file.size +		local all = { start = 0, finish = size } +		if range == nil then +			range = all +		end +		local command = type(cmd) == "function" and cmd(win, range, pos) or cmd +		local check_same = (options and options.check_same ~= nil) and options.check_same or global_options.check_same +		local check = check_same == true or (type(check_same) == "number" and check_same >= size) +		local status, out, err = vis:pipe(win.file, all, command) +		if status ~= 0 then +			vis:message(err) +		elseif out == nil or out == "" then +			vis:info("No output from formatter") +		elseif not check or win.file:content(all) ~= out then +			local start, finish = range.start, range.finish +			win.file:delete(range) +			win.file:insert(start, out:sub(start + 1, finish + (out:len() - size))) +		end +		return pos +	end +	return { +		apply = apply, +		options = options or { ranged = type(cmd) == "function" }, +	} +end + +local function with_filename(win, option) +	if win.file.path then +		return option .. "'" .. win.file.path:gsub("'", "\\'") .. "'" +	else +		return "" +	end +end + +local formatters = {} +formatters = { +	bash = stdio_formatter(function(win) +		return "shfmt " .. with_filename(win, "--filename ") .. " -" +	end), +	csharp = stdio_formatter("dotnet csharpier"), +	go = stdio_formatter("gofmt"), +	lua = { +		pick = function(win) +			local _, out = vis:pipe( +				win.file, +				{ start = 0, finish = win.file.size }, +				"test -e .lua-format && echo luaformatter || echo stylua" +			) +			return formatters[out:gsub("\n$", "")] +		end, +	}, +	luaformatter = stdio_formatter("lua-format"), +	markdown = stdio_formatter(function(win) +		if win.options and win.options.colorcolumn ~= 0 then +			return "prettier --parser markdown --prose-wrap always " +				.. ("--print-width " .. (win.options.colorcolumn - 1) .. " ") +				.. with_filename(win, "--stdin-filepath ") +		else +			return "prettier --parser markdown " .. with_filename(win, "--stdin-filepath ") +		end +	end, { ranged = false }), +	powershell = stdio_formatter([[ +    "$( (command -v powershell.exe || command -v pwsh) 2>/dev/null )" -c ' +        Invoke-Formatter  -ScriptDefinition ` +          ([IO.StreamReader]::new([Console]::OpenStandardInput()).ReadToEnd()) +      ' | sed -e :a -e '/^[\r\n]*$/{$d;N;};/\n$/ba' +  ]]), +	rust = stdio_formatter("rustfmt"), +	stylua = stdio_formatter(function(win, range) +		if range and (range.start ~= 0 or range.finish ~= win.file.size) then +			return "stylua -s --range-start " +				.. range.start +				.. " --range-end " +				.. range.finish +				.. with_filename(win, " --stdin-filepath ") +				.. " -" +		else +			return "stylua -s " .. with_filename(win, "--stdin-filepath ") .. " -" +		end +	end), +	text = stdio_formatter(function(win) +		if win.options and win.options.colorcolumn ~= 0 then +			return "fmt -w " .. (win.options.colorcolumn - 1) +		else +			return "fmt" +		end +	end, { ranged = false }), +} + +local function getwinforfile(file) +	for win in vis:windows() do +		if win and win.file and win.file.path == file.path then +			return win +		end +	end +end + +local function apply(file_or_keys, range, pos) +	local win = type(file_or_keys) ~= "string" and getwinforfile(file_or_keys) or vis.win +	local ret = type(file_or_keys) ~= "string" and function() +		return pos +	end or function() +		return 0 +	end +	pos = pos or win.selection.pos +	local formatter = formatters[win.syntax] +	if formatter and formatter.pick then +		formatter = formatter.pick(win) +	end +	if formatter == nil then +		vis:info("No formatter for " .. win.syntax) +		return ret() +	end +	if range ~= nil and not formatter.options.ranged and range.start ~= 0 and range.finish ~= win.file.size then +		vis:info("Formatter for " .. win.syntax .. " does not support ranges") +		return ret() +	end +	pos = formatter.apply(win, range, pos) or pos +	vis:redraw() +	win.selection.pos = pos +	return ret() +end + +return { +	formatters = formatters, +	options = global_options, +	apply = apply, +	stdio_formatter = stdio_formatter, +	with_filename = with_filename, +} diff --git a/config/essentials/vis/fzf-mru.lua b/config/essentials/vis/fzf-mru.lua new file mode 100644 index 0000000..6c2510d --- /dev/null +++ b/config/essentials/vis/fzf-mru.lua @@ -0,0 +1,75 @@ +local module = {} +module.fzfmru_filepath = os.getenv("XDG_CACHE_HOME") .. "/vis-fzf-mru" +module.fzfmru_path = "fzf" +module.fzfmru_args = "--height=40%" +module.fzfmru_history = 20 + +local function read_mru() +    local mru = {} +    local f = io.open(module.fzfmru_filepath) +    if f == nil then return end +    for line in f:lines() do table.insert(mru, line) end +    f:close() + +    return mru +end + +local function write_mru(win) +    local file_path = win.file.path +    local mru = read_mru() + +    -- check if mru data exists +    if mru == nil then mru = {} end +    -- check if we opened any file +    if file_path == nil then return end +    -- check duplicate +    if file_path == mru[1] then return end + +    local f = io.open(module.fzfmru_filepath, "w+") +    if f == nil then return end + +    table.insert(mru, 1, file_path) + +    for i, k in ipairs(mru) do +        if i > module.fzfmru_history then break end +        if i == 1 or k ~= file_path then +            f:write(string.format("%s\n", k)) +        end +    end + +    f:close() +end + +vis.events.subscribe(vis.events.WIN_OPEN, write_mru) + +vis:command_register("fzfmru", function(argv) +    local command = "cat " .. module.fzfmru_filepath .. " | " .. +                        module.fzfmru_path .. " " .. module.fzfmru_args .. " " .. +                        table.concat(argv, " ") + +    local file = io.popen(command) +    local output = file:read() +    local _, _, status = file:close() + +    if status == 0 then +        vis:command(string.format("e '%s'", output)) +    elseif status == 1 then +        vis:info(string.format( +                     "fzf-open: No match. Command %s exited with return value %i.", +                     command, status)) +    elseif status == 2 then +        vis:info(string.format( +                     "fzf-open: Error. Command %s exited with return value %i.", +                     command, status)) +    elseif status ~= 130 then +        vis:info(string.format( +                     "fzf-open: Unknown exit status %i. command %s exited with return value %i", +                     status, command, status, status)) +    end + +    vis:redraw() + +    return true +end) + +return module diff --git a/config/essentials/vis/fzf-open.lua b/config/essentials/vis/fzf-open.lua new file mode 100644 index 0000000..1c9d1e6 --- /dev/null +++ b/config/essentials/vis/fzf-open.lua @@ -0,0 +1,81 @@ +-- Copyright (C) 2017  Guillaume Chérel +-- Copyright (C) 2023  Matěj Cepl +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Lesser General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU Lesser General Public License for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with this program.  If not, see <https://www.gnu.org/licenses/>. +local M = {} + +M.fzf_path = "fzf" +M.fzf_args = "--height=40%" + +vis:command_register("fzf", function(argv) +	local fzf_path = M.fzf_path +	if argv[1] == "--search-path" then +		table.remove(argv, 1) +		local dir = table.remove(argv, 1) +		fzf_path = ([[FZF_DEFAULT_COMMAND="$FZF_DEFAULT_COMMAND --search-path ]] .. dir .. [[" fzf]]) +	end + +	local command = string.gsub( +		[[ +            $fzf_path \ +                --header="Enter:edit,^s:split,^v:vsplit" \ +                --expect="ctrl-s,ctrl-v" \ +                $fzf_args $args +        ]], +		"%$([%w_]+)", +		{ +			fzf_path = fzf_path, +			fzf_args = M.fzf_args, +			args = table.concat(argv, " "), +		} +	) + +	local file = io.popen(command) +	local output = {} +	for line in file:lines() do +		table.insert(output, line) +	end +	local _, _, status = file:close() + +	if status == 0 then +		local action = "e" + +		if output[1] == "ctrl-s" then +			action = "split" +		elseif output[1] == "ctrl-v" then +			action = "vsplit" +		end + +		vis:feedkeys(string.format(":%s '%s'<Enter>", action, output[2])) +	elseif status == 1 then +		vis:info(string.format("fzf-open: No match. Command %s exited with return value %i.", command, status)) +	elseif status == 2 then +		vis:info(string.format("fzf-open: Error. Command %s exited with return value %i.", command, status)) +	elseif status ~= 130 then +		vis:info( +			string.format( +				"fzf-open: Unknown exit status %i. command %s exited with return value %i", +				status, +				command, +				status +			) +		) +	end + +	vis:redraw() + +	return true +end, "Select file to open with fzf") + +return M diff --git a/config/essentials/vis/themes/nord.lua b/config/essentials/vis/themes/nord.lua new file mode 100644 index 0000000..71635cf --- /dev/null +++ b/config/essentials/vis/themes/nord.lua @@ -0,0 +1,123 @@ +-- base16-vis (https://github.com/pshevtsov/base16-vis) +-- by Petr Shevtsov +-- Nord scheme by arcticicestudio +local lexers = vis.lexers + +local colors = { +    ['bg'] = '#2E3440', +    ['black'] = '#3B4252', +    ['light_black'] = '#434C5E', +    ['dark_gray'] = '#4C566A', +    ['gray'] = '#D8DEE9', +    ['light_gray'] = '#616E88', +    ['fg'] = '#E5E9F0', +    ['white'] = '#ECEFF4', +    ['turquoise'] = '#8FBCBB', +    ['light_cyan'] = '#88C0D0', +    ['cyan'] = '#81A1C1', +    ['blue'] = '#5E81AC', +    ['red'] = '#BF616A', +    ['orange'] = '#D08770', +    ['yellow'] = '#EBCB8B', +    ['green'] = '#A3BE8C', +    ['magenta'] = '#B48EAD' +} + +lexers.colors = colors + +local fg = 'fore:' .. colors.fg +local bg = 'back:' .. colors.bg + +lexers.STYLE_DEFAULT = bg .. ',' .. fg +lexers.STYLE_NOTHING = bg +lexers.STYLE_CLASS = 'fore:' .. colors.blue +lexers.STYLE_COMMENT = 'fore:' .. colors.light_gray .. ',italics' +lexers.STYLE_CONSTANT = 'fore:' .. colors.cyan +lexers.STYLE_DEFINITION = 'fore:' .. colors.green +lexers.STYLE_ERROR = 'fore:' .. colors.light_cyan .. ',italics' +lexers.STYLE_FUNCTION = 'fore:' .. colors.light_cyan .. ',bold' +lexers.STYLE_HEADING = 'fore:' .. colors.bg .. ',back:' .. colors.yellow +lexers.STYLE_KEYWORD = 'fore:' .. colors.cyan .. ',bold' +lexers.STYLE_LABEL = 'fore:' .. colors.blue +lexers.STYLE_NUMBER = 'fore:' .. colors.magenta +lexers.STYLE_OPERATOR = 'fore:' .. colors.light_cyan +lexers.STYLE_REGEX = 'fore:' .. colors.orange +lexers.STYLE_STRING = 'fore:' .. colors.green +lexers.STYLE_PREPROCESSOR = 'fore:' .. colors.blue +lexers.STYLE_TAG = 'fore:' .. colors.blue +lexers.STYLE_TYPE = 'fore:' .. colors.cyan +lexers.STYLE_VARIABLE = 'fore:' .. colors.cyan .. ',bold' +lexers.STYLE_WHITESPACE = 'fore:' .. colors.light_black +lexers.STYLE_EMBEDDED = 'fore:' .. colors.magenta +lexers.STYLE_IDENTIFIER = fg .. ',bold' + +lexers.STYLE_LINENUMBER = 'fore:' .. colors.light_black .. ',back:' .. colors.bg +lexers.STYLE_CURSOR = 'fore:' .. colors.bg .. ',back:' .. colors.fg +lexers.STYLE_CURSOR_PRIMARY = 'fore:' .. colors.bg .. ',back:' .. colors.fg +lexers.STYLE_CURSOR_LINE = 'back:' .. colors.black +lexers.STYLE_COLOR_COLUMN = 'back:' .. colors.black +lexers.STYLE_SELECTION = 'back:' .. colors.light_black +lexers.STYLE_STATUS = 'fore:' .. colors.gray .. ',back:' .. colors.black +lexers.STYLE_STATUS_FOCUSED = 'fore:' .. colors.cyan .. ',back:' .. colors.black +lexers.STYLE_SEPARATOR = lexers.STYLE_DEFAULT +lexers.STYLE_INFO = 'fore:default,back:default,bold' +lexers.STYLE_EOF = '' + +-- lexer specific styles + +-- Diff +lexers.STYLE_ADDITION = 'back:' .. colors.green .. ',fore:' .. colors.bg +lexers.STYLE_DELETION = 'back:' .. colors.red .. ',fore:' .. colors.bg +lexers.STYLE_CHANGE = 'back:' .. colors.yellow .. ',fore:' .. colors.bg + +-- CSS +lexers.STYLE_PROPERTY = lexers.STYLE_ATTRIBUTE +lexers.STYLE_PSEUDOCLASS = '' +lexers.STYLE_PSEUDOELEMENT = '' + +-- HTML +lexers.STYLE_TAG_UNKNOWN = lexers.STYLE_TAG .. ',italics' +lexers.STYLE_ATTRIBUTE_UNKNOWN = lexers.STYLE_ATTRIBUTE .. ',italics' + +-- Latex, TeX, and Texinfo +lexers.STYLE_COMMAND = lexers.STYLE_KEYWORD +lexers.STYLE_COMMAND_SECTION = lexers.STYLE_CLASS +lexers.STYLE_ENVIRONMENT = lexers.STYLE_TYPE +lexers.STYLE_ENVIRONMENT_MATH = lexers.STYLE_NUMBER + +-- Makefile +lexers.STYLE_TARGET = '' + +-- Markdown +lexers.STYLE_HR = '' +lexers.STYLE_HEADING_H1 = 'fore:' .. colors.orange .. ',bold' +lexers.STYLE_HEADING_H2 = 'fore:' .. colors.red .. ',bold' +for i = 3, 6 do +    lexers['STYLE_HEADING_H' .. i] = 'fore:' .. colors.magenta .. ',bold' +end +lexers.STYLE_BOLD = 'bold' +lexers.STYLE_ITALIC = 'italics' +lexers.STYLE_LIST = lexers.STYLE_KEYWORD +lexers.STYLE_LINK = 'fore:' .. colors.yellow .. ',italics' +lexers.STYLE_REFERENCE = 'fore:' .. colors.blue +lexers.STYLE_CODE = 'back:' .. colors.black .. ',fore:' .. colors.turquoise + +-- Output +lexers.STYE_FILENAME = 'bold' +lexers.STYLE_LINE = 'fore:' .. colors.green +lexers.STYLE_COLUMN = 'underline' +lexers.STYLE_MESSAGE = '' + +-- Python +lexers.STYLE_KEYWORD_SOFT = '' + +-- YAML +lexers.STYLE_ERROR_INDENT = 'back:' .. colors.red + +-- GO +lexers.STYLE_CONSTANT_BUILTIN = 'fore:' .. colors.yellow +lexers.STYLE_FUNCTION_METHOD = 'fore:' .. colors.light_cyan +lexers.STYLE_FUNCTION_BUILTIN = 'fore:' .. colors.light_cyan .. ',bold' + +-- Lua +lexers.STYLE_ATTRIBUTE = 'fore:' .. colors.yellow .. ',bold' diff --git a/config/essentials/vis/title.lua b/config/essentials/vis/title.lua new file mode 100644 index 0000000..d743b63 --- /dev/null +++ b/config/essentials/vis/title.lua @@ -0,0 +1,16 @@ +require('vis') + +local function set_title(title) +    -- print() cannot be used here as it will mess up vis +    vis:command(string.format(":!printf '\\033]2;vis %s\\007'", title)) +end + +vis.events.subscribe(vis.events.INIT, function() print('\27[22;2t') end) + +vis.events.subscribe(vis.events.WIN_OPEN, +                     function(win) set_title(win.file.name or '[No Name]') end) + +vis.events.subscribe(vis.events.FILE_SAVE_POST, +                     function(file) set_title(file.name) end) + +vis.events.subscribe(vis.events.QUIT, function() print('\27[23;2t') end) diff --git a/config/essentials/vis/vis-go.lua b/config/essentials/vis/vis-go.lua new file mode 100644 index 0000000..b0b383f --- /dev/null +++ b/config/essentials/vis/vis-go.lua @@ -0,0 +1,102 @@ +local function jump_to(path, line, col) +	if path then +		vis:command(string.format("e %s", path)) +	end +	vis.win.selection:to(line, col) +end + +local Gostack = { s = {}, i = 1 } + +function Gostack:push(v) +	self.s[self.i] = v +	self.i = self.i + 1 +end + +function Gostack:pop() +	if self.i == 1 then +		return nil +	end +	self.i = self.i - 1 +	return self.s[self.i] +end + +local function godef() +	local win = vis.win +	if win.syntax ~= "go" then +		return 0 +	end + +	local file = win.file +	local pos = win.selection.pos +	local cmd = string.format("godef -i -o %d", pos) +	local status, out, err = vis:pipe(file, { start = 0, finish = file.size }, cmd) +	if status ~= 0 or not out then +		if err then +			vis:info(err) +		end +		return status +	end + +	Gostack:push({ path = file.path, line = win.selection.line, col = win.selection.col }) + +	local path, line, col = string.match(out, "([^:]+):([^:]+):([^:]+)") +	if not path then +		-- same file +		line, col = string.match(out, "([^:]+):([^:]+)") +	end +	jump_to(path, line, col) +end + +local function godef_back() +	if vis.win.syntax ~= "go" then +		return 0 +	end + +	local pos = Gostack:pop() +	if pos then +		jump_to(pos.path, pos.line, pos.col) +	end +end + +vis:map(vis.modes.NORMAL, "gd", godef, "Jump to Go symbol/definition") +vis:map(vis.modes.NORMAL, "gD", godef_back, "Jump back to previous Go symbol/definition") + +local function gorename(argv, force, win, selection) +	if win.syntax ~= "go" then +		return true +	end + +	local name = argv[1] +	if not name then +		vis:info("empty new name provided") +		return false +	end + +	local forceFlag = "" +	if force then +		forceFlag = "-force" +	end + +	local pos = selection.pos +	local f = +		io.popen(string.format("gorename -offset %s:#%d -to %s %s 2>&1", win.file.path, pos, name, forceFlag), "r") +	local out = f:read("*all") +	local success, _, _ = f:close() +	if not success then +		vis:message(out) +		return false +	end + +	-- refresh current file +	vis:command("e") +	win.selection.pos = pos + +	vis:info(out) +	return true +end + +vis:command_register( +	"gorename", +	gorename, +	"Perform precise type-safe renaming of identifiers in Go source code: :gorename newName" +) diff --git a/config/essentials/vis/vis-ultisnips/init.lua b/config/essentials/vis/vis-ultisnips/init.lua new file mode 100644 index 0000000..52faa55 --- /dev/null +++ b/config/essentials/vis/vis-ultisnips/init.lua @@ -0,0 +1,149 @@ +-------------------------------------------------------------------------------- +-- Modules + +local M = {} +local cwd = ... +local SnipMate  = require(cwd .. '.snipmate-parser') +local UltiSnips = require(cwd .. '.ultisnips-parser') + + + +-------------------------------------------------------------------------------- +-- Config + +M.snipmate  = '' +M.ultisnips = '' + + + +-------------------------------------------------------------------------------- +-- Helper functions + +-- Takes list of snippets and concatenates them into the string suitable +-- for passing to dmenu (or, very probably, vis-menu) +local function snippetslist(snippets) +  local list = '' + +  for k,v in pairs(snippets) do +    if not v.description then +      list = list .. k .. '\n' +    else +      list = list .. k .. ' - ' .. v.description .. '\n' +    end +  end + +  return list +end + + + +local function load_ultisnips() +  local snippetfile = M.ultisnips .. vis.win.syntax .. '.snippets' +  local snippets, success = UltiSnips.load_snippets(snippetfile) +  if not success then +    vis:info('Failed to load a correct UltiSnip: ' .. snippetfile) +  end +  return snippets, success +end + + + +local function load_snipmate() +  local snippetfile = M.snipmate .. vis.win.syntax .. '.snippets' +  local snippets, success = SnipMate.load_snippets(snippetfile) +  if not success then +    vis:info('Failed to load a correct SnipMate: ' .. snippetfile) +  end +  return snippets, success +end + + + +-- Second will append to first using suffix for distinguishing +local function merge_and_override(snips1, snips2, suffix) +  for k,v in pairs(snips2) do +    snips1[k .. suffix] = v +  end +  return snips1 +end + + + +-------------------------------------------------------------------------------- +-- Plugging it all in + +vis:map(vis.modes.INSERT, "<C-x><C-j>", function() +  local snippets = merge_and_override(load_snipmate(), load_ultisnips(), '_us') + +  local win = vis.win +  local file = win.file +  local pos = win.selection.pos + +  if not pos then +    return +  end +  -- TODO do something clever here + +  -- Use prefix W if exists +  local initial = ' ' +  local range = file:text_object_longword(pos > 0 and pos - 1 or pos) +  if range then +      initial = initial .. file:content(range) +  end + +  -- Note, for one reason or another, using vis-menu corrupts my terminal +  -- (urxvt) for exact amount of lines that vis-menu takes +  -- dmenu has no such problems, but can't take initial input :-\ +  --local stdout = io.popen("echo '" .. snippetslist(snippets) .. "' | dmenu -l 5", "r") +  local stdout = io.popen("echo '" .. snippetslist(snippets) .. "' | vis-menu " .. initial, "r") +  local chosen = stdout:lines()() +  local _, msg, status = stdout:close() +  if status ~= 0 or not chosen then +    vis:message(msg) +    return +  end + +  local trigger = chosen:gmatch('[^ ]+')() +  local snipcontent = snippets[trigger].content +  if range then +    file:delete(range) +    -- Update position after deleting the range +    pos = pos - (range.finish - range.start) +    vis:redraw() +  end + +  vis:insert(snipcontent.str) + + +  if #snipcontent.tags > 0 then +    vis:info("Use 'g>' and 'g<' to navigate between anchors.") + +    -- Create selections iteratively using `:#n,#n2 p` command and `gs` to +    -- save it in the jumplist +    for _,v in ipairs(snipcontent.tags) do +      -- Can't use 'x' command because it'd select stuff across +      -- whole file +      vis:command('#' .. pos + v.selstart ..',#' .. pos + v.selend .. ' p') +      --vis:feedkeys('gs') -- Tested, works without this too, but just in case +      --vis:message('Command: ' .. cmd) +    end + +    -- Backtrack through all selections we've made first +    -- (so that we can use g> to move us forward)... +    for _ in ipairs(snipcontent.tags) do +      vis:feedkeys('g<') +    end + +    -- ... then set us on the first selection +    vis:feedkeys('g>') +  else +    win.selection.pos = pos + #snipcontent.str +  end +end, "Insert a snippet") + + + +-------------------------------------------------------------------------------- +-- End module + +return M
\ No newline at end of file diff --git a/config/essentials/vis/vis-ultisnips/snipmate-parser.lua b/config/essentials/vis/vis-ultisnips/snipmate-parser.lua new file mode 100644 index 0000000..9d735f1 --- /dev/null +++ b/config/essentials/vis/vis-ultisnips/snipmate-parser.lua @@ -0,0 +1,128 @@ +-------------------------------------------------------------------------------- +-- Module table + +local M = {} + +local lpeg = require('lpeg') + + + +-------------------------------------------------------------------------------- +-- lpeg rules + +-- Base definitions +-- local tws                = lpeg.S' ' ^ 1 +local tnewline           = lpeg.S'\n' +-- local tlowcasedword      = lpeg.R'az' ^ 1 +local tdigit             = lpeg.locale()['digit'] +local talphanum          = lpeg.locale()['alnum'] +local tanyprintable      = lpeg.locale()['print'] +-- local tcontrol           = lpeg.locale()['cntrl'] +local ttabtrigger        = tanyprintable ^ 1 +local ttag               = lpeg.Cg(lpeg.Cp(), 'selstart') +                           * lpeg.P'${' +                           * lpeg.Cg(tdigit^1, 'tag-order') +                           * ( +                               (lpeg.S':' * lpeg.Cg(talphanum^1, 'default-value') * lpeg.S'}') +                               + lpeg.S'}' +                             ) +                           * lpeg.Cg(lpeg.Cp(), 'selend') +local tsnippetdecl       = lpeg.P'snippet' * lpeg.S' ' * lpeg.Cg(ttabtrigger, 'tabtrigger') * tnewline +local tsnippetcontent    = lpeg.C( +                             lpeg.Cp() * +                             (lpeg.S'\t '^1 +                              * (lpeg.Ct(ttag) + tanyprintable)^1 +                              * tnewline +                             )^1 +                           ) + +-- Constructs +local tsnippet = tsnippetdecl * tsnippetcontent +local tcomment = lpeg.S'#' * tanyprintable^0 * tnewline + +-- The way grammar captures: +-- Every snippet gets its own table, and every table has: +-- 'tabtrigger' - the tabtrigger +-- [1]          - full content +-- [2]          - start of snippet content (need to subtract from selstart/selend +-- [3..n]       - tags +local tsnippetsfile = lpeg.Ct((tcomment + lpeg.Ct(tsnippet) + tnewline) ^1) + +-------------------------------------------------------------------------------- +-- Functions + +local function trim_tabs(content) +  local trim = function (s) +    return (string.gsub(s, "^\t(.-)$", "%1")) +  end + +  local ret='' +  for str in string.gmatch(content, '([^\n]+)') do +    ret = ret .. trim(str) .. '\n' +  end +  return ret +end + +-- Tags are on the top level of th table, +-- defined starting with index '3' +-- Index '2' is start of the content +-- Structure: +-- { tag-order: int +-- , selstart: int +-- , selend: int +-- , default-value: str +-- } +local function extract_tags(tableau) +  local tags = {} +  for k,v in ipairs(tableau) do +    if k >= 3 then -- Only process starting with ix 2 +      tags[k - 2] = { selstart = v.selstart - tableau[2] - 1 +                    , selend   = v.selend   - tableau[2] - 1 +                    , default  = v['default-value'] +                    , order    = v['tag-order'] +                    } +-- vis:message('snippet ' .. tableau.tabtrigger .. ' tag ' .. +--                 tostring(tags[k - 1].order) .. ' has start/end: ' .. +--                 tostring(tags[k - 1].selstart) .. '/' .. +--                 tostring(tags[k - 1].selend)) +    end +  end +  return tags +end + +M.load_snippets = function(snippetfile) +  local snippets = {} + +  local f = io.open(snippetfile, 'r') +  if f then +    local content = f:read("*all") + +    -- TODO hmmm, this'll make whole file unsuable, when it could +    --      in fact have usable snippets +    local m = tsnippetsfile:match(content) +    if not m then +      vis:info('Failed to parse SnipMate file: '.. snippetfile) +      return nil +    else +      -- k is index of snippet definition, v is table of snippet def +      for _,v in pairs(m) do +        snippets[v.tabtrigger] = { description = nil +                                 , options     = {} +                                 , content = { str  = trim_tabs(v[1]) +                                             , tags = extract_tags(v) +                                             } +                                 } +      end +    end + +    f:close() +    return snippets, true +  else +    return snippets, false +  end +end + +-------------------------------------------------------------------------------- +-- End module + +return M diff --git a/config/essentials/vis/vis-ultisnips/testlpeg-snipmate.lua b/config/essentials/vis/vis-ultisnips/testlpeg-snipmate.lua new file mode 100644 index 0000000..997365f --- /dev/null +++ b/config/essentials/vis/vis-ultisnips/testlpeg-snipmate.lua @@ -0,0 +1,160 @@ +local lpeg = require("lpeg") + +-------------------------------------------------------------------------------- + +-- Base definitions +-- local tws = lpeg.S(" ") ^ 1 +local tnewline = lpeg.S("\n") +-- local tlowcasedword = lpeg.R("az") ^ 1 +local tdigit = lpeg.locale()["digit"] +local talphanum = lpeg.locale()["alnum"] +local tanyprintable = lpeg.locale()["print"] +-- local tcontrol = lpeg.locale()["cntrl"] +local ttabtrigger = tanyprintable ^ 1 +local ttag = lpeg.Cg(lpeg.Cp(), "selstart") +	* lpeg.P("${") +	* lpeg.Cg(tdigit ^ 1, "tag-order") +	* ((lpeg.S(":") * lpeg.Cg(talphanum ^ 1, "default-value") * lpeg.S("}")) + lpeg.S("}")) +	* lpeg.Cg(lpeg.Cp(), "selend") +local tsnippetdecl = lpeg.P("snippet") * lpeg.S(" ") * lpeg.Cg(ttabtrigger, "tabtrigger") * tnewline +local tsnippetcontent = lpeg.C(lpeg.Cp() * (lpeg.S("\t ") ^ 1 * (lpeg.Ct(ttag) + tanyprintable) ^ 1 * tnewline) ^ 1) + +-- Constructs +local tsnippet = tsnippetdecl * tsnippetcontent +local tcomment = lpeg.S("#") * tanyprintable ^ 0 * tnewline + +-- The way grammar captures: +-- Every snippet gets its own table, and every table has: +-- 'tabtrigger' - the tabtrigger +-- [1]          - full content +-- [2..n]       - tags +local tsnippetsfile = lpeg.Ct((tcomment + lpeg.Ct(tsnippet) + tnewline) ^ 1) +-------------------------------------------------------------------------------- + +-- local testsingle = [[ +-- snippet sim +--         ${1:public} static int Main(string[] args) +--         { +--                 ${0} +--                 return 0; +--         } +-- ]] + +-- local testmulti = [[ +-- snippet sim +--         ${1:public} static int Main(string[] args) +--         { +--                 ${0} +--                 return 0; +--         } +-- snippet simc +--         public class Application +--         { +--                 ${1:public} static int Main(string[] args) +--                 { +--                         ${0} +--                         return 0; +--                 } +--         } +-- snippet svm +--         ${1:public} static void Main(string[] args) +--         { +--                 ${0} +--         } +-- ]] + +local testfile = [[ +# I'll most propably add more stuff in here like +# * List/Array constructio +# * Mostly used generics +# * Linq +# * Funcs, Actions, Predicates +# * Lambda +# * Events +# +# Feedback is welcome! +# +# Main +snippet sim +        ${1:public} static int Main(string[] args) +        { +                ${0} +                return 0; +        } +snippet simc +        public class Application +        { +                ${1:public} static int Main(string[] args) +                { +                        ${0} +                        return 0; +                } +        } +snippet svm +        ${1:public} static void Main(string[] args) +        { +                ${0} +        } +# if condition +snippet if +        if (${1:true}) +        { +                ${0:${VISUAL}} +        } +snippet el +        else +        { +                ${0:${VISUAL}} +        } +]] + +-------------------------------------------------------------------------------- +-- Test + +local function print_table(tableau, tabwidth) +	if tabwidth == nil then +		tabwidth = 0 +	end + +	-- Iterate +	for k, v in pairs(tableau) do +		local tabs = ("\t"):rep(tabwidth) + +		print(tabs .. k .. ':"' .. tostring(v) .. '"') +		if type(v) == "table" then +			print_table(v, tabwidth + 1) +		end +	end +end + +--print("------------ header ------------------------------------") +--p = lpeg.Ct(tsnippetdecl) +--t = p:match([[ +--snippet classy +--]]) +--print_table(t) +--print("--------------------------------------------------------------") + +--print("------------ tag ------------------------------------") +--print_table( +--  lpeg.Ct(ttag):match('${0:VISUAL}') +--) +--print_table( +--  lpeg.Ct(ttag):match('${12:Badonkadong}') +--) +--print_table( +--  lpeg.Ct(ttag):match('${1}') +--) +--print("--------------------------------------------------------------") + +--print("------------ single snippet test ------------------------------------") +--print_table(lpeg.Ct(tsnippet):match(testsingle)) +--print("--------------------------------------------------------------") + +--print("------------ multi snippet test ------------------------------------") +--print_table(lpeg.Ct(tsnippetsfile):match(testmulti)) +--print("--------------------------------------------------------------") + +print("------------ file with comments -------------------------------------") +print_table(tsnippetsfile:match(testfile)) +print("--------------------------------------------------------------") diff --git a/config/essentials/vis/vis-ultisnips/testlpeg-ultisnips.lua b/config/essentials/vis/vis-ultisnips/testlpeg-ultisnips.lua new file mode 100644 index 0000000..79df900 --- /dev/null +++ b/config/essentials/vis/vis-ultisnips/testlpeg-ultisnips.lua @@ -0,0 +1,230 @@ +local lpeg = require("lpeg") + +-------------------------------------------------------------------------------- + +local tsep = lpeg.S(" ") +local tws = tsep ^ 1 +local tnewline = lpeg.S("\n") +local tlowcasedword = lpeg.R("az") ^ 1 +local tdigit = lpeg.locale()["digit"] +-- local talphanum          = lpeg.locale()['alnum'] +local tanyprintable = lpeg.locale()["print"] +local tcontrol = lpeg.locale()["cntrl"] +local function quoted(p) +	return lpeg.S('"') * p * lpeg.S('"') +end +local function anythingbut(ch) +	return (tanyprintable + tcontrol) - lpeg.S(ch) +end + +local ttabtriggercomplex = quoted(tlowcasedword * lpeg.S("()[]?0123456789-") ^ 1) +-- TODO This is just retarded +local ttabtriggerweird = lpeg.S("!") * (lpeg.R("az") + lpeg.S("?()")) ^ 1 * lpeg.S("!") +local ttabtriggerweird2 = lpeg.P("#!") +local ttabtrigger = ttabtriggercomplex + ttabtriggerweird + ttabtriggerweird2 + tlowcasedword +local tdescription = quoted(lpeg.Cg((tanyprintable - lpeg.S('"')) ^ 1, "description")) +local toption = lpeg.R("az") + +local tstartsnippet = lpeg.P("snippet") +	* tws +	* lpeg.Cg(ttabtrigger, "tabtrigger") +	* tws +	* tdescription +	* tws ^ 0 +	* lpeg.Cg(toption ^ 0, "options") +local tendsnippet = lpeg.P("endsnippet") + +-- The content parsing needs cleanup, its really convoluted due to me learning +-- lpeg while using it +--tcontent      = ((tanyprintable + tcontrol)^1 - tendsnippet) * tnewline +local tcontent = ((lpeg.S(" \t") + tanyprintable) ^ 1 - tendsnippet) * tnewline +local tsnippet = tstartsnippet * tnewline * ((tendsnippet * tnewline) + lpeg.Cg(tcontent ^ 1, "content")) + +local tcomment = lpeg.S("#") * tanyprintable ^ 0 * tnewline +local tpriority = lpeg.P("priority") * tws * lpeg.Cg(lpeg.S("-") ^ 0 * tdigit ^ 1, "priority") + +-- TODO doesn't work +local tsnippetsfile = (lpeg.Ct(tsnippet) + tpriority + tcomment + tnewline) ^ 1 + +-- TODO does parse values correctly, but parsing out nested tags will +--      require recursion at the callsite since I have no clue how to do it +local ttag = { +	"T", +	Expr = lpeg.C((lpeg.V("T") + anythingbut("}")) ^ 1), +	Tnum = lpeg.Cg(tdigit ^ 1, "tagnum"), +	Ps = lpeg.Cg(lpeg.Cp(), "selstart"), +	Pe = lpeg.Cg(lpeg.Cp(), "selend"), +	Tc = lpeg.V("Ps") +		* lpeg.P("${") +		* lpeg.V("Tnum") +		* lpeg.S(":") +		* lpeg.Cg(lpeg.V("Expr"), "expr") +		* lpeg.V("Pe") +		* lpeg.S("}"), +	Ts = lpeg.V("Ps") * lpeg.S("$") * lpeg.V("Pe") * lpeg.V("Tnum"), +	T = lpeg.V("Tc") + lpeg.V("Ts"), +} + +-------------------------------------------------------------------------------- + +-- local testheader = [[ +-- snippet #! "#!/usr/bin/env lua" b +-- ]] + +local testcontent = [[ +for ${1:idx},${2:val} in ipairs(${3:table_name}) do +	$0 +end +]] + +local testsnippet = [[ +snippet fori "ipair for foop" b +for ${1:idx},${2:val} in ipairs(${3:table_name}) do +	$0 +end +endsnippet +]] + +local luasnippetfile = [[ +priority -50 + +################################# +# Snippets for the Lua language # +################################# +snippet #! "#!/usr/bin/env lua" b +#!/usr/bin/env lua +$0 +endsnippet + +snippet !fun(ction)?! "New function" br +local function ${1:new_function}(${2:args}) +	$0 +end +endsnippet + +snippet forp "pair for loop" b +for ${1:name},${2:val} in pairs(${3:table_name}) do +	$0 +end +endsnippet + +snippet fori "ipair for foop" b +for ${1:idx},${2:val} in ipairs(${3:table_name}) do +	$0 +end +endsnippet + +snippet for "numeric for loop" b +for ${1:i}=${2:first},${3:last}${4/^..*/(?0:,:)/}${4:step} do +	$0 +end +endsnippet + +snippet do "do block" +do +	$0 +end +endsnippet + +snippet repeat "repeat loop" b +repeat +	$1 +until $0 +endsnippet + +snippet while "while loop" b +while $1 do +	$0 +end +endsnippet + +snippet if "if statement" b +if $1 then +	$0 +end +endsnippet + +snippet ife "if/else statement" b +if $1 then +	$2 +else +	$0 +end +endsnippet + +snippet eif "if/elseif statement" b +if $1 then +	$2 +elseif $3 then +	$0 +end +endsnippet + +snippet eife "if/elseif/else statement" b +if $1 then +	$2 +elseif $3 then +	$4 +else +	$0 +end +endsnippet + +snippet pcall "pcall statement" b +local ok, err = pcall(${1:your_function}) +if not ok then +	handler(${2:ok, err}) +${3:else +	success(${4:ok, err}) +}end +endsnippet + +snippet local "local x = 1" +local ${1:x} = ${0:1} +endsnippet + +# vim:ft=snippets: +]] + +-------------------------------------------------------------------------------- +-- Test + +local function print_table(tableau, tabwidth) +	if tabwidth == nil then +		tabwidth = 0 +	end + +	-- Iterate +	for k, v in pairs(tableau) do +		local tabs = ("\t"):rep(tabwidth) + +		print(tabs .. k .. ': "' .. tostring(v) .. '"') +		if type(v) == "table" then +			print_table(v, tabwidth + 1) +		end +	end +end + +do +	print("------------ snippet test ------------------------------------") +	local p = lpeg.Ct(tsnippet) +	local t = p:match(testsnippet) +	print_table(t) +	print("--------------------------------------------------------------") +end + +do +	print("------------ snippetfile test ------------------------------------") +	local p = lpeg.Ct(tsnippetsfile) +	local t = p:match(luasnippetfile) +	print_table(t) +	print("--------------------------------------------------------------") +end + +do +	print("------------ tags test -------------------------------------") +	local p = lpeg.Ct((lpeg.Ct(ttag) + tanyprintable + tcontrol) ^ 1) +	local t = p:match(testcontent) +	print_table(t) +	print("--------------------------------------------------------------") +end diff --git a/config/essentials/vis/vis-ultisnips/ultisnips-parser.lua b/config/essentials/vis/vis-ultisnips/ultisnips-parser.lua new file mode 100644 index 0000000..a4240b8 --- /dev/null +++ b/config/essentials/vis/vis-ultisnips/ultisnips-parser.lua @@ -0,0 +1,211 @@ +-------------------------------------------------------------------------------- +-- Module table + +local M = {} + +local lpeg = require('lpeg') + + + +-------------------------------------------------------------------------------- +-- lpeg rules + +local tsep               = lpeg.S' \t' +local tws                = tsep ^ 1 +local tnewline           = lpeg.S'\n' +local tlowcasedword      = lpeg.R'az' ^ 1 +local tdigit             = lpeg.locale()['digit'] +-- local talphanum          = lpeg.locale()['alnum'] +local tanyprintable      = lpeg.locale()['print'] +local tcontrol           = lpeg.locale()['cntrl'] +local function surrounded(ch, p) return lpeg.S(ch) * p * lpeg.S(ch) end +local function anythingbut(ch) return (tanyprintable + tcontrol) - lpeg.S(ch) end + +local ttabtriggercomplex = surrounded ('"', +                              tlowcasedword * lpeg.S'()[]?0123456789-'^1 +                           ) +-- TODO This is just retarded +--      Check the actual grammar and see what special starting chars are +--      then relax the grammar a bit +local ttabtriggerweird   = surrounded('!', +                             (lpeg.R'az' + lpeg.S'?()') ^ 1 +                           ) +local ttabtriggerweird2  = lpeg.P'#!' +local ttabtriggerweird3  = surrounded('/', +                             (anythingbut'/') ^1 +                           ) +local ttabtrigger        = ttabtriggercomplex +                         + ttabtriggerweird +                         + ttabtriggerweird2 +                         + ttabtriggerweird3 +                         + (tlowcasedword + lpeg.S'.') +local tdescription       = surrounded ('"', +                              lpeg.Cg( (tanyprintable - lpeg.S'"')^1, 'description') +                           ) +local toption            = lpeg.R'az' + +local tstartsnippet = lpeg.P'snippet' +                    * tws +                    * lpeg.Cg(ttabtrigger, 'tabtrigger') +                    * tws +                    * tdescription +                    * tws ^ 0 +                    * lpeg.Cg(toption^0, 'options') +local tendsnippet   = lpeg.P'endsnippet' + +-- The content parsing needs cleanup, its really convoluted due to me learning +-- lpeg while using it +--tcontent      = ((tanyprintable + tcontrol)^1 - tendsnippet) * tnewline +local tcontent = ((lpeg.S' \t' + tanyprintable)^1 - tendsnippet) +               * tnewline +local tsnippet = tstartsnippet +               * tnewline +               * ((tendsnippet * tnewline) + lpeg.Cg(tcontent ^ 1, 'content')) + +-- local tcomment  = lpeg.S'#' +--                 * tanyprintable^0 +--                 * tnewline +-- local tpriority = lpeg.P'priority' +--                 * tws +--                 * lpeg.Cg(lpeg.S('-')^0 * tdigit^1, 'priority') + +-- TODO doesn't work +-- local tsnippetsfile = (lpeg.Ct(tsnippet) + tpriority + tcomment + tnewline) ^ 1 + + +-- TODO does parse values correctly, but parsing out nested tags will +--      require recursion at the callsite since I have no clue how to do it +local ttag = { 'T' +       ; Expr = lpeg.C((lpeg.V'T' + ((tanyprintable + tcontrol) - lpeg.S'}'))^1) +       , Tnum = lpeg.Cg(tdigit ^ 1, 'tagnum') +       , Ps   = lpeg.Cg(lpeg.Cp(), 'selstart') +       , Pe   = lpeg.Cg(lpeg.Cp(), 'selend') +       , Tc   = lpeg.V'Ps' +                * lpeg.P'${' +                * lpeg.V'Tnum' +                * lpeg.S(':') +                * lpeg.Cg(lpeg.V'Expr', 'expr') +                * lpeg.V'Pe' +                * lpeg.S'}' +       , Ts   = lpeg.V'Ps' * lpeg.S'$' * lpeg.V'Pe' * lpeg.V'Tnum' +       , T    = lpeg.V'Tc' + lpeg.V'Ts' +       } + + + +-------------------------------------------------------------------------------- +-- Functions + +-- Parses the snippet's content to create a table we later use +-- to corrently insert the text, the selections, and the default values +local function create_content(str) +  local content = {} +  content.str   = str +  content.tags  = {} + +  local p = vis.lpeg.Ct((lpeg.Ct(ttag) + tanyprintable + tcontrol) ^ 1) +  local m = p:match(str) + +  local s = 1 -- We start from 1 to adjust position from $^0 to ^$0 +  for k,v in ipairs(m) do +    content.tags[k] = v +    -- TODO recurse over tag.expr to extract nested tags +    --      Of course this will actually have to be used later on, depending +    --      on whether the tag is added or not + +    -- We need to keep track of how much we remove, and readjust all +    -- subsequent selection points +    -- Note to self, I hate all this bookkeeping +    local tagtext = string.sub(str, v.selstart, v.selend) +    if v.expr ~= nil then +      content.str = string.gsub(content.str, tagtext, v.expr) +      content.tags[k].selstart = content.tags[k].selstart - s +      content.tags[k].selend   = content.tags[k].selstart + #v.expr +      s = s + #'${' + #tostring(k) + #':' + 1 +    else +      content.str = string.gsub(content.str, tagtext, '') +      content.tags[k].selstart = content.tags[k].selstart - s +      content.tags[k].selend   = content.tags[k].selstart +      s = s + #'$' + 1 +    end +  end + +  return content +end + + + +-- Takes a line starting with 'snippet' and a lines iterator, and creates +-- a 'snippet' table to be used +-- If it fails it returns nil, otherwise returns two values, a tabtrigger +-- and a snippet +local function create_snippet(start_line, linesit) +  local snippetstr = start_line .. '\n' +  -- Read content into list of lines until we hit `endsnippet` +  for line in linesit do +    local s, _ = string.find(line, 'endsnippet') +    if s == 1 then +      snippetstr = snippetstr .. 'endsnippet' .. '\n' +      break +    else +      snippetstr = snippetstr .. line .. '\n' +    end +  end + +  local p = vis.lpeg.Ct(tsnippet) +  local m = p:match(snippetstr) + +  if not m then +    -- Enable this when debugging, otherwise it nukes whole app +    vis:info('Failed to parse some snippets!') +    -- vis:message('Failed to parse snippet: ' .. snippetstr) +    return nil +  else +    local tabtrigger = m.tabtrigger +    local snippet = {} +    snippet.description = m.description +    snippet.options = m.options +    snippet.content = create_content(m.content) +    return tabtrigger, snippet +  end +end + + + +-- Loads all snippets from passed '.snippets' file. Should probably be +-- triggered when new file is loaded or when syntax is set/changed +M.load_snippets = function(snippetfile) +  local snippets = {} + +  local f = io.open(snippetfile, 'r') +  if f then +    io.input(f) +    local linesit = io.lines() + +    for line in linesit do +      -- TODO read whole file, then apply lpeg grammar that parses all +      -- snippets out rather than being pedestrian about it like this +      local s, _ = string.find(line, 'snippet') +      -- Find lines that start with 'snippet' and enter +      -- snippet reading loop +      if s == 1 then +        local tabtrigger, snippet = create_snippet(line, linesit) +        if tabtrigger then +          snippets[tabtrigger] = snippet +        end +      end +    end + +    io.close(f) +    return snippets, true +  else +    return snippets, false +  end +end + + + +-------------------------------------------------------------------------------- +-- End module + +return M
\ No newline at end of file diff --git a/config/essentials/vis/visrc.lua b/config/essentials/vis/visrc.lua new file mode 100644 index 0000000..e35b436 --- /dev/null +++ b/config/essentials/vis/visrc.lua @@ -0,0 +1,152 @@ +------------------------------------ +--- REQUIRES +------------------------------------ +require("vis") + +-- plugins +require("build") +-- use Trash directory instead, remove set_dir function +require("backup") +require("cursors") +require("title") +require("commentary") +require("complete-line") +-- removed formatting because already fulfilled by format.lua +require("vis-go") +-- set height to 40% +require("fzf-open") +require("vis-ultisnips") +-- TODO: doesn't work when using with 'e|b' +-- require("yank-highlight") + +-- save position before formatting, use vis:redraw +local format = require("format") + +-- set height to 40% +local fzfmru = require("fzf-mru") +fzfmru.fzfmru_path = 'grep "^' .. os.getenv("PWD") .. '" | fzf' + + +-- todo: +-- c-scope +-- c-tags +-- ... +-- vis-goto, favor open-file-under-cursor +-- ... +-- ultisnips +-- ... +-- vis-yank-highlight + +------------------------------------ +--- VARIABLES +------------------------------------ +local m = vis.modes + +------------------------------------ +--- FUNCTIONS +------------------------------------ + +local function map_cmd(mode, map, command, help) +	vis:map(mode, map, function() +		vis:command(command) +	end, help) +end + +-- TOOD: use window selection to restore position +local function wrap_restore(f, ...) +	local pos = vis.win.selection.pos +	f(...) +	vis.win.selection.pos = pos +end + +local function map_keys(mode, map, keys, help) +	vis:map(mode, map, function() +		vis:feedkeys(keys) +	end, help) +end + +------------------------------------ +--- COMMANDS +----------------------------------- + +vis:command_register("make", function() +	vis:command("!make && head -n 1") +end, "make") +vis:command_register("Q", function() +	vis:command("qa!") +end, "Quit all") +vis:command_register("delws", function() +	vis:command(",x/[ \t]+$|^[ \t]+$/d") +end, "Remove trailing whitespace") + +------------------------------------- +--- MAPPINGS +------------------------------------- + +vis:map(m.NORMAL, "<C-p>", function() vis:command("fzf") end, "Open file with fzf") + + +vis:map(m.NORMAL, " r", function() +	wrap_restore(vis.command, vis, "e $vis_filepath") +end, "Reload active file") + +vis:map(m.NORMAL, "=", format.apply, "Format active file") + +map_cmd(m.NORMAL, " c", "e ~/.config/vis/visrc.lua", "Edit config file") +map_cmd(m.NORMAL, " q", "q!", "Quit (force)") +map_cmd(m.NORMAL, " s", "!doas vis $vis_filepath", "Edit as superuser") +map_cmd(m.NORMAL, " w", "w", "Write") +map_cmd(m.NORMAL, " x", "!chmod u+x $vis_filepath", "Make active file executable") + +vis:map(m.NORMAL, " eh", function() +	vis:command("!lowdown $vis_filepath > ${vis_filepath%.md}.html") +	vis:info("exported.") +end, "Export markdown to html") + +map_keys(m.NORMAL, " nl", ":<seq -f '%0.0f. ' 1 ", "Insert numbered list") + +-- select markdown list element:	,x/^(\d+\.|[-*])\s+.+\n(^ .+\n)*/ + +------------------------------------ +--- EVENTS +------------------------------------ + +vis.events.subscribe(vis.events.INIT, function() +	vis.options.ignorecase = true +	vis.options.autoindent = true +	vis.options.shell = "/bin/sh" +	local theme = "nord" +	vis:command("set theme " .. theme) +end) + +vis.events.subscribe(vis.events.WIN_OPEN, function(win) -- luacheck: no unused args +	win.options.relativenumbers = true + +	if win.syntax == "bash" then +		map_keys( +			m.NORMAL, +			";p", +			"V:x/^(\\s*)(.+)$/ c/\\1>\\&2 printf '\\2: %s\\\\n' \"$\\2\"/<Enter><Escape>", +			"Print variable" +		) +		map_keys( +			m.NORMAL, +			";v", +			"V:x/^(\\s*)(.+)$/ c/\\1\"$(\\2)\"/<Enter><Escape>", +			"Surround in variable" +		) +		map_keys( +			m.NORMAL, +			";|", +			"V:x/\\| / c/|\n\t/<Enter><Escape>", +			"Wrap one-line multi pipe command" +		) +		map_keys( +			m.NORMAL, +			";e", +			"V:x/^(\\s*)(.+)$/ c/\\1[ \"\\2\" ] || exit 1/<Enter><Escape>", +			"Condition exit if empty" +		) + +	end +end) diff --git a/config/essentials/vis/yank-highlight.lua b/config/essentials/vis/yank-highlight.lua new file mode 100644 index 0000000..37a9578 --- /dev/null +++ b/config/essentials/vis/yank-highlight.lua @@ -0,0 +1,37 @@ +require("vis") + +local M = { +	style = "reverse", -- Style used for highlighting +	duration = 0.2,    -- [s] Time to remain highlighted (10 ms precision) +} + +vis.events.subscribe(vis.events.INIT, function() +	local yank = vis:action_register("highlighted-yank", function() +		vis.win:style_define(vis.win.STYLE_SELECTION, M.style) +		vis:redraw() +		local tstamp = os.clock() +		while os.clock() - tstamp < M.duration do end +		vis.win:style_define(vis.win.STYLE_SELECTION, vis.lexers.STYLE_SELECTION) +		vis:redraw() +		vis:feedkeys("<vis-operator-yank>") +	end, "Yank operator with highlighting") +	vis:map(vis.modes.OPERATOR_PENDING, "y", yank) +	vis:map(vis.modes.VISUAL, "y", yank) +	vis:map(vis.modes.VISUAL_LINE, "y", yank) + +	vis:map(vis.modes.NORMAL, "y", function(keys) +		local sel_end_chrs = "$%^{}()wp" +		if #keys < 1 or sel_end_chrs:find(keys:sub(-1), 1, true) == nil then +			if keys:find("<Escape>") then +				return #keys +			end +			return -1 +		end +		vis:feedkeys("<vis-mode-visual-charwise>") +		vis:feedkeys(keys) +		vis:feedkeys("y<Escape>") +		return #keys +	end) +end) + +return M diff --git a/config/essentials/zsh/.zshrc b/config/essentials/zsh/.zshrc index 545f9cc..5981fd9 100644 --- a/config/essentials/zsh/.zshrc +++ b/config/essentials/zsh/.zshrc @@ -7,8 +7,6 @@ then  	[ "${TTY%%tty*}" = '/dev/' ] && clear  	case "${TTY#/dev/tty}" in  		1) exec startx > /dev/null 2>&1 ;; -		2) exec startdwl > /dev/null 2>&1 ;; -		3) exec startw > /dev/null 2>&1 ;;  		*) false ;;  	esac && exit  fi @@ -18,10 +16,12 @@ autoload -z edit-command-line  zle -N edit-command-line  ### Source files -. $XDG_CONFIG_HOME/zsh/comp.zsh -. $XDG_CONFIG_HOME/shell/functions.sh -. $XDG_CONFIG_HOME/shell/aliases.sh -. $XDG_CONFIG_HOME/zsh/widgets.zsh +source_it() { [ -f "$1" ] && . "$1" } +source_it /etc/profile.d/plan9.sh +source_it $XDG_CONFIG_HOME/zsh/comp.zsh +source_it $XDG_CONFIG_HOME/shell/functions.sh +source_it $XDG_CONFIG_HOME/shell/aliases.sh +source_it $XDG_CONFIG_HOME/zsh/widgets.zsh  # . $XDG_CONFIG_HOME/zsh/prompt.zsh  # . $XDG_CONFIG_HOME/zsh/plugins.zsh @@ -31,6 +31,7 @@ eval "$(zoxide init zsh)"  ### Plugins  [ -f "$HOME/.local/share/zap/zap.zsh" ] && source "$HOME/.local/share/zap/zap.zsh" +# plug "MichaelAquilina/zsh-you-should-use"  plug "chivalryq/git-alias"  # plug "marlonrichert/zsh-autocomplete"  plug "zap-zsh/fzf" @@ -41,7 +42,7 @@ plug "zsh-users/zsh-completions"  plug "MichaelAquilina/zsh-auto-notify"  export AUTO_NOTIFY_TITLE="zsh"  export AUTO_NOTIFY_BODY="%command [%exit_code]" -AUTO_NOTIFY_IGNORE+=("gurk" "ttyper" "pulsemixer" "tmux" "btop" "vis") +AUTO_NOTIFY_IGNORE+=("gurk" "ttyper" "pulsemixer" "tmux" "btop" "vis" "clock")  # Substring search settings  export HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND="bg=blue,fg=black,bold" @@ -58,7 +59,7 @@ fi  # Add nnn shell level to prompt -[ -n "$NNNLVL" ] && PS1="N$NNNLVL $PS1" +[ -n "$NNNLVL" ] && PS1="N$NNNLVL$PS1"  # cd on nnn quiting  nnn_cd () diff --git a/config/extra/mutt/.gitignore b/config/extra/mutt/.gitignore new file mode 100644 index 0000000..ad7bbfd --- /dev/null +++ b/config/extra/mutt/.gitignore @@ -0,0 +1,2 @@ +cache +mailboxes diff --git a/config/extra/mutt/muttrc b/config/extra/mutt/muttrc new file mode 100644 index 0000000..a5cfa90 --- /dev/null +++ b/config/extra/mutt/muttrc @@ -0,0 +1 @@ +source /home/aluc/.config/mutt/configs/raymaekers.luca@gmail.com diff --git a/config/home/.zshenv b/config/home/.zshenv index 1d732ab..34a7c2f 100644 --- a/config/home/.zshenv +++ b/config/home/.zshenv @@ -81,8 +81,8 @@ export FZF_DEFAULT_OPTS=$FZF_DEFAULT_OPTS'  export LESS="-i -r"  # Colored manpages -export MANPAGER="less -R --use-color -Dd+r -Du+b" -export MANROFFOPT="-P -c" +# export MANPAGER="less -R --use-color -Dd+r -Du+b" +# export MANROFFOPT="-P -c"  export CM_LAUNCHER="commander -c" @@ -96,3 +96,6 @@ export PATH="$PATH:$GOPATH/bin"  export PLAN9=/usr/lib/plan9  export PATH="$PATH:$PLAN9/bin" + +export SSH_ASKPASS=askpass +export SSH_ASKPASS_REQUIRE=prefer diff --git a/config/wayland/gammastep/config.ini b/config/wayland/gammastep/config.ini index 3a80417..e0699ac 100644 --- a/config/wayland/gammastep/config.ini +++ b/config/wayland/gammastep/config.ini @@ -1,7 +1,7 @@  [general]  fade=0  location-provider=manual -adjustment-method=wayland +# adjustment-method=wayland  gamma=0.8  temp-day=5700  temp-night=3600 diff --git a/config/wayland/hypr/hyprland.conf b/config/wayland/hypr/hyprland.conf index 0a00b31..d084eb9 100644 --- a/config/wayland/hypr/hyprland.conf +++ b/config/wayland/hypr/hyprland.conf @@ -31,7 +31,7 @@ general {      layout = dwindle -	cursor_inactive_timeout = 0 +	# cursor_inactive_timeout = 0  }  misc { | 
