diff options
137 files changed, 13615 insertions, 0 deletions
diff --git a/.config/aerc/aerc.conf b/.config/aerc/aerc.conf new file mode 100644 index 0000000..855ce3d --- /dev/null +++ b/.config/aerc/aerc.conf @@ -0,0 +1,148 @@ +# +# aerc main configuration + +[ui] +# +# Describes the format for each row in a mailbox view. This field is compatible +# with mutt's printf-like syntax. +# +# Default: +index-format=%D %-40.40n %s + +# +# See time.Time#Format at https://godoc.org/time#Time.Format +# +# Default: 2006-01-02 03:04 PM (ISO 8601 + 12 hour time) +timestamp-format=2006-01-02 03:04 PM + +# +# Width of the sidebar, including the border. +# +# Default: 20 +sidebar-width=25 + +# +# Message to display when viewing an empty folder. +# +# Default: (no messages) +empty-message=(no messages) + +# Message to display when no folders exists or are all filtered +# +# Default: (no folders) +empty-dirlist=(no folders) + +# Enable mouse events in the ui, e.g. clicking and scrolling with the mousewheel +# +# Default: false +mouse-enabled=false + +# +# Ring the bell when new messages are received +# +# Default: yes +new-message-bell=true + +# Describes the format string to use for the directory list +# +# Default: %n %>r +dirlist-format=%n %>r + +[viewer] +# +# Specifies the pager to use when displaying emails. Note that some filters +# may add ANSI codes to add color to rendered emails, so you may want to use a +# pager which supports ANSI codes. +# +# Default: less -R +pager=less -g -i -M -R -w -X -s + +# +# If an email offers several versions (multipart), you can configure which +# mimetype to prefer. For example, this can be used to prefer plaintext over +# html emails. +# +# Default: text/plain,text/html +alternatives=text/plain,text/html + +# +# Default setting to determine whether to show full headers or only parsed +# ones in message viewer. +# +# Default: false +show-headers=false + +# +# Layout of headers when viewing a message. To display multiple headers in the +# same row, separate them with a pipe, e.g. "From|To". Rows will be hidden if +# none of their specified headers are present in the message. +# +# Default: From|To,Cc|Bcc,Date,Subject +header-layout=From|To,Cc|Bcc,Date,Subject + +# Whether to always show the mimetype of an email, even when it is just a single part +# +# Default: false +always-show-mime=true + +[compose] +# +# Specifies the command to run the editor with. It will be shown in an embedded +# terminal, though it may also launch a graphical window if the environment +# supports it. Defaults to $EDITOR, or vi. +editor= + +# +# Default header fields to display when composing a message. To display +# multiple headers in the same row, separate them with a pipe, e.g. "To|From". +# +# Default: To|From,Subject +header-layout=To|From,Subject + +[filters] +# +# Filters allow you to pipe an email body through a shell command to render +# certain emails differently, e.g. highlighting them with ANSI escape codes. +# +# The first filter which matches the email's mimetype will be used, so order +# them from most to least specific. +# +# You can also match on non-mimetypes, by prefixing with the header to match +# against (non-case-sensitive) and a comma, e.g. subject,text will match a +# subject which contains "text". Use header,~regex to match against a regex. +subject,~^\[PATCH=awk -f /usr/share/aerc/filters/hldiff +text/html=/usr/share/aerc/filters/html +text/*=awk -f /usr/share/aerc/filters/plaintext +#image/*=catimg -w $(tput cols) - +application/pdf=/usr/share/aerc/filters/pdf + +[triggers] +# +# Triggers specify commands to execute when certain events occur. +# +# Example: +# new-email=exec notify-send "New email from %n" "%s"<Enter> + +# +# Executed when a new email arrives in the selected folder +new-email=exec notify-send -a aerc -i mail-client "New mail from %n" "%s"<Enter> + +[templates] +# Templates are used to populate email bodies automatically. +# + +# The directories where the templates are stored. It takes a colon-separated +# list of directories. +# +# default: /usr/share/aerc/templates/ +template-dirs=/usr/share/aerc/templates/:/home/robert/.config/aerc/templates + +# The template to be used for quoted replies. +# +# default: quoted_reply +quoted-reply=quoted_reply + +# The template to be used for forward as body. +# +# default: forward_as_body +forwards=forward_as_body diff --git a/.config/aerc/binds.conf b/.config/aerc/binds.conf new file mode 100644 index 0000000..6037267 --- /dev/null +++ b/.config/aerc/binds.conf @@ -0,0 +1,101 @@ +# Binds are of the form <key sequence> = <command to run> +# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>" +# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit +<C-p> = :prev-tab<Enter> +<C-n> = :next-tab<Enter> +<C-t> = :term<Enter> + +[messages] +q = :quit<Enter> + +j = :next<Enter> +<Down> = :next<Enter> +<C-d> = :next 50%<Enter> +<C-f> = :next 100%<Enter> +<PgDn> = :next -s 100%<Enter> + +k = :prev<Enter> +<Up> = :prev<Enter> +<C-u> = :prev 50%<Enter> +<C-b> = :prev 100%<Enter> +<PgUp> = :prev -s 100%<Enter> +g = :select 0<Enter> +G = :select -1<Enter> + +J = :next-folder<Enter> +K = :prev-folder<Enter> + +<Enter> = :view<Enter> +# d = :confirm 'Really delete this message?' ':delete-message<Enter>'<Enter> +d = :mv Trash<Enter> +D = :delete<Enter> +a = :archive year<Enter> + +C = :compose<Enter> + +rr = :reply -a<Enter> +rq = :reply -aq<Enter> +Rr = :reply<Enter> +Rq = :reply -q<Enter> + +c = :cf<space> +$ = :term<space> +! = :term<space> +| = :pipe<space> + +/ = :search<space> +\ = :filter<space> +n = :next-result<Enter> +N = :prev-result<Enter> + +[view] +q = :close<Enter> +| = :pipe<space> +D = :delete<Enter> +S = :save<space> +A = :archive year<Enter> + +f = :forward<Enter> +rr = :reply -a<Enter> +rq = :reply -aq<Enter> +Rr = :reply<Enter> +Rq = :reply -q<Enter> + +H = :toggle-headers<Enter> +<C-k> = :prev-part<Enter> +<C-j> = :next-part<Enter> +J = :next<Enter> +K = :prev<Enter> + +[compose] +# Keybindings used when the embedded terminal is not selected in the compose +# view +$ex = <C-x> +<C-k> = :prev-field<Enter> +<C-j> = :next-field<Enter> +<tab> = :next-field<Enter> + +[compose::editor] +# Keybindings used when the embedded terminal is selected in the compose view +$noinherit = true +$ex = <C-x> +<C-k> = :prev-field<Enter> +<C-j> = :next-field<Enter> +<C-p> = :prev-tab<Enter> +<C-n> = :next-tab<Enter> + +[compose::review] +# Keybindings used when reviewing a message to be sent +y = :send<Enter> +n = :abort<Enter> +q = :abort<Enter> +e = :edit<Enter> +a = :attach<Enter> +d = :save-draft<Enter> + +[terminal] +$noinherit = true +$ex = <C-x> + +<C-p> = :prev-tab<Enter> +<C-n> = :next-tab<Enter> diff --git a/.config/alacritty/alacritty.yml b/.config/alacritty/alacritty.yml new file mode 100644 index 0000000..4d826d2 --- /dev/null +++ b/.config/alacritty/alacritty.yml @@ -0,0 +1,539 @@ +# Configuration for Alacritty, the GPU enhanced terminal emulator. + +# Any items in the `env` entry below will be added as +# environment variables. Some entries may override variables +# set by alacritty itself. +env: + # TERM variable + # + # This value is used to set the `$TERM` environment variable for + # each instance of Alacritty. If it is not present, alacritty will + # check the local terminfo database and use 'alacritty' if it is + # available, otherwise 'xterm-256color' is used. + # + # TERM: xterm-256color + TERM: alacritty + # COLORTERM: truecolor + +window: + # Window dimensions (changes require restart) + # + # Specified in number of columns/lines, not pixels. + # If both are `0`, this setting is ignored. + dimensions: + columns: 80 + lines: 24 + + # Window position (changes require restart) + # + # Specified in number of pixels. + # If the position is not set, the window manager will handle the placement. + #position: + # x: 0 + # y: 0 + + # Window padding (changes require restart) + # + # Blank space added around the window in pixels. This padding is scaled + # by DPI and the specified value is always added at both opposing sides. + padding: + x: 0 + y: 0 + + # Spread additional padding evenly around the terminal content. + dynamic_padding: false + + # Window decorations + # + # Values for `decorations`: + # - full: Borders and title bar + # - none: Neither borders nor title bar + decorations: full + + # Startup Mode (changes require restart) + # + # Values for `startup_mode`: + # - Windowed + # - Maximized + # - Fullscreen + # + # Values for `startup_mode` (macOS only): + # - SimpleFullscreen + #startup_mode: Windowed + # + # Window title + # title: Alacritty + + # Window class (Linux only): + class: + # Application instance name + instance: Alacritty + # General application class + general: Alacritty + + # GTK theme variant (Linux only) + # + # Override the variant of the GTK theme. Commonly supported values are `dark` and `light`. + # Set this to `None` to use the default theme variant. + gtk_theme_variant: None + +#scrolling: + # Maximum number of lines in the scrollback buffer. + # Specifying '0' will disable scrolling. + #history: 10000 + + # Number of lines the viewport will move for every line scrolled when + # scrollback is enabled (history > 0). + #multiplier: 3 + + # Scroll to the bottom when new text is written to the terminal. + auto_scroll: true + +# Spaces per Tab (changes require restart) +# +# This setting defines the width of a tab in cells. +# +# Some applications, like Emacs, rely on knowing about the width of a tab. +# To prevent unexpected behavior in these applications, it's also required to +# change the `it` value in terminfo when altering this setting. +tabspaces: 8 + +# Font configuration (changes require restart) +# +# Important font attributes like antialiasing, subpixel aa, and hinting can be +# controlled through fontconfig. Specifically, the following attributes should +# have an effect: +# - hintstyle +# - antialias +# - lcdfilter +# - rgba +# +# For instance, if you wish to disable subpixel antialiasing, you might set the +# rgba property to `none`. If you wish to completely disable antialiasing, you +# can set antialias to `false`. +# +# Please see these resources for more information on how to use fontconfig: +# - https://wiki.archlinux.org/index.php/font_configuration#Fontconfig_configuration +# - file:///usr/share/doc/fontconfig/fontconfig-user.html +font: + # Normal (roman) font face + normal: + family: Iosevka + + # The `style` can be specified to pick a specific face. + style: Slim + + # Bold font face + bold: + style: Bold + + # Italic font face + italic: + style: Italic + + bold_italic: + style: Bold Italic + + # Point size + size: 13.0 + + # Offset is the extra space around each character. `offset.y` can be thought of + # as modifying the line spacing, and `offset.x` as modifying the letter spacing. + #offset: + # x: 0 + # y: 0 + + # Glyph offset determines the locations of the glyphs within their cells with + # the default being at the bottom. Increasing `x` moves the glyph to the right, + # increasing `y` moves the glyph upwards. + #glyph_offset: + # x: 0 + # y: 0 + + # Thin stroke font rendering (macOS only) + # + # Thin strokes are suitable for retina displays, but for non-retina screens + # it is recommended to set `use_thin_strokes` to `false` + # + # macOS >= 10.14.x: + # + # If the font quality on non-retina display looks bad then set + # `use_thin_strokes` to `true` and enable font smoothing by running the + # following command: + # `defaults write -g CGFontRenderingFontSmoothingDisabled -bool NO` + # + # This is a global setting and will require a log out or restart to take + # effect. + #use_thin_strokes: true + +# If `true`, bold text is drawn using the bright color variants. +draw_bold_text_with_bright_colors: false + +# Colors (Papercolor) +colors: + # Default colors + primary: + background: &bg '#1c1c1c' + foreground: &fg '#d0d0d0' + + # Bright and dim foreground colors + # + # The dimmed foreground color is calculated automatically if it is not present. + # If the bright foreground color is not set, or `draw_bold_text_with_bright_colors` + # is `false`, the normal foreground color will be used. + # dim_foreground: '#BDC0BA' + # bright_foreground: '#ffffff' # just white ;) + + # Cursor colors + # + # Colors which should be used to draw the terminal cursor. If these are unset, + # the cursor color will be the inverse of the cell color. + cursor: + text: *bg + cursor: *fg + + # Selection colors + # + # Colors which should be used to draw the selection area. If selection + # background is unset, selection color will be the inverse of the cell colors. + # If only text is unset the cell text color will remain the same. + selection: + text: '#251c16' + background: '#fffffc' + + # Normal colors + normal: + black: '#1c1c1c' + red: '#af005f' + green: '#5faf00' + yellow: '#d7af5f' + blue: '#5fafd7' + magenta: '#808080' + cyan: '#d7875f' + white: '#d0d0d0' + + # Bright colors + bright: + black: '#585858' + red: '#5faf5f' + green: '#afd700' + yellow: '#af87d7' + blue: '#ffaf00' + magenta: '#00afaf' + cyan: '#5f8787' + white: '#fffffc' + + # Dim colors + # + # If the dim colors are not set, they will be calculated automatically based + # on the `normal` colors. + #dim: + # black: '0x000000' + # red: '0x8c3336' + # green: '0x7a8530' + # yellow: '0x97822e' + # blue: '0x506d8f' + # magenta: '0x80638e' + # cyan: '0x497e7a' + # white: '0x9a9a9a' + + # Indexed Colors + # + # The indexed colors include all colors from 16 to 256. + # When these are not set, they're filled with sensible defaults. + # + # Example: + # - { index: 16, color: '0x000000' } + indexed_colors: [] + +# Visual Bell +# +# Any time the BEL code is received, Alacritty "rings" the visual bell. Once +# rung, the terminal background will be set to white and transition back to the +# default background color. You can control the rate of this transition by +# setting the `duration` property (represented in milliseconds). You can also +# configure the transition function by setting the `animation` property. +# +# Values for `animation`: +# - Ease +# - EaseOut +# - EaseOutSine +# - EaseOutQuad +# - EaseOutCubic +# - EaseOutQuart +# - EaseOutQuint +# - EaseOutExpo +# - EaseOutCirc +# - Linear +# +# Specifying a `duration` of `0` will disable the visual bell. +visual_bell: + duration: 0 + color: '#af005f' + # bell_command: { program: "mpv", args: ["--no-config", "--no-idle", "/usr/share/sounds/gnome/default/alerts/glass.ogg"] } + +# Background opacity +# +# Window opacity as a floating point number from `0.0` to `1.0`. +# The value `0.0` is completely transparent and `1.0` is opaque. +background_opacity: 0.99 + +selection: + semantic_escape_chars: ",│`|:\"' ()[]{}<>" + + # When set to `true`, selected text will be copied to both the primary and + # the selection clipboard. Otherwise, it will only be copied to the selection + # clipboard. + save_to_clipboard: false + +dynamic_title: true + +cursor: + # Cursor style + # + # Values for `style`: + # - ▇ Block + # - _ Underline + # - | Beam + style: Block + + # If this is `true`, the cursor will be rendered as a hollow box when the + # window is not focused. + unfocused_hollow: true + +# Live config reload (changes require restart) +#live_config_reload: true + +# Shell +# +# You can set `shell.program` to the path of your favorite shell, e.g. `/bin/fish`. +# Entries in `shell.args` are passed unmodified as arguments to the shell. +# +# Default: +# - (macOS) /bin/bash --login +# - (Linux) user login shell +# - (Windows) powershell +#shell: +# program: /bin/bash +# args: +# - --login + +# Startup directory +# +# Directory the shell is started in. If this is unset, or `None`, the working +# directory of the parent process will be used. +#working_directory: None + +# Windows 10 ConPTY backend (Windows only) +# +# This will enable better color support and may resolve other issues, +# however this API and its implementation is still young and so is +# disabled by default, as stability may not be as good as the winpty +# backend. +# +# Alacritty will fall back to the WinPTY automatically if the ConPTY +# backend cannot be initialized. +#enable_experimental_conpty_backend: false + +# Send ESC (\x1b) before characters when alt is pressed. +#alt_send_esc: true + +#debug: + # Display the time it takes to redraw each frame. + #render_timer: false + + # Keep the log file after quitting Alacritty. + #persistent_logging: false + + # Log level + # + # Values for `log_level`: + # - None + # - Error + # - Warn + # - Info + # - Debug + # - Trace + #log_level: Warn + + # Print all received window events. + #print_events: false + + # Record all characters and escape sequences as test data. + #ref_test: false + +mouse: + # Click settings + # + # The `double_click` and `triple_click` settings control the time + # alacritty should wait for accepting multiple clicks as one double + # or triple click. + #double_click: { threshold: 300 } + #triple_click: { threshold: 300 } + + # If this is `true`, the cursor is temporarily hidden when typing. + #hide_when_typing: true + + url: + # URL launcher + # + # This program is executed when clicking on a text which is recognized as a URL. + # The URL is always added to the command as the last parameter. + # + # When set to `None`, URL launching will be disabled completely. + # + # Default: + # - (macOS) open + # - (Linux) xdg-open + # - (Windows) explorer + launcher: + program: url-launcher + args: [] + + # URL modifiers + # + # These are the modifiers that need to be held down for opening URLs when clicking + # on them. The available modifiers are documented in the key binding section. + modifiers: Control + +# Mouse bindings +# +# Mouse bindings are specified as a list of objects, much like the key +# bindings further below. +# +# Each mouse binding will specify a: +# +# - `mouse`: +# +# - Middle +# - Left +# - Right +# - Numeric identifier such as `5` +# +# - `action` (see key bindings) +# +# And optionally: +# +# - `mods` (see key bindings) +#mouse_bindings: +# - { mouse: Middle, action: PasteSelection } + +# Key bindings +# +# Key bindings are specified as a list of objects. For example, this is the +# default paste binding: +# +# `- { key: V, mods: Control|Shift, action: Paste }` +# +# Each key binding will specify a: +# +# - `key`: Identifier of the key pressed +# +# - A-Z +# - F1-F24 +# - Key0-Key9 +# +# A full list with available key codes can be found here: +# https://docs.rs/glutin/*/glutin/event/enum.VirtualKeyCode.html#variants +# +# Instead of using the name of the keys, the `key` field also supports using +# the scancode of the desired key. Scancodes have to be specified as a +# decimal number. This command will allow you to display the hex scancodes +# for certain keys: +# +# `showkey --scancodes`. +# +# Then exactly one of: +# +# - `chars`: Send a byte sequence to the running application +# +# The `chars` field writes the specified string to the terminal. This makes +# it possible to pass escape sequences. To find escape codes for bindings +# like `PageUp` (`"\x1b[5~"`), you can run the command `showkey -a` outside +# of tmux. Note that applications use terminfo to map escape sequences back +# to keys. It is therefore required to update the terminfo when changing an +# escape sequence. +# +# - `action`: Execute a predefined action +# +# - Copy +# - Paste +# - PasteSelection +# - IncreaseFontSize +# - DecreaseFontSize +# - ResetFontSize +# - ScrollPageUp +# - ScrollPageDown +# - ScrollLineUp +# - ScrollLineDown +# - ScrollToTop +# - ScrollToBottom +# - ClearHistory +# - Hide +# - Quit +# - ToggleFullscreen +# - SpawnNewInstance +# - ClearLogNotice +# - ReceiveChar +# - None +# +# (macOS only): +# - ToggleSimpleFullscreen: Enters fullscreen without occupying another space +# +# - `command`: Fork and execute a specified command plus arguments +# +# The `command` field must be a map containing a `program` string and an +# `args` array of command line parameter strings. For example: +# `{ program: "alacritty", args: ["-e", "vttest"] }` +# +# And optionally: +# +# - `mods`: Key modifiers to filter binding actions +# +# - Command +# - Control +# - Option +# - Super +# - Shift +# - Alt +# +# Multiple `mods` can be combined using `|` like this: +# `mods: Control|Shift`. +# Whitespace and capitalization are relevant and must match the example. +# +# - `mode`: Indicate a binding for only specific terminal reported modes +# +# This is mainly used to send applications the correct escape sequences +# when in different modes. +# +# - AppCursor +# - AppKeypad +# - Alt +# +# A `~` operator can be used before a mode to apply the binding whenever +# the mode is *not* active, e.g. `~Alt`. +# +# Bindings are always filled by default, but will be replaced when a new +# binding with the same triggers is defined. To unset a default binding, it can +# be mapped to the `ReceiveChar` action. Alternatively, you can use `None` for +# a no-op if you do not wish to receive input characters for that binding. +key_bindings: + - { key: V, mods: Super, action: Paste } + - { key: C, mods: Super, action: Copy } + - { key: Insert, mods: Shift, action: PasteSelection } + - { key: Key0, mods: Control, action: ResetFontSize } + - { key: Equals, mods: Control, action: IncreaseFontSize } + - { key: Add, mods: Control, action: IncreaseFontSize } + - { key: Subtract, mods: Control, action: DecreaseFontSize } + - { key: Minus, mods: Control, action: DecreaseFontSize } + - { key: Return, mods: Alt, action: ToggleFullscreen } + + - { key: W, mods: Super|Shift, action: Quit } + - { key: N, mods: Super|Shift, action: SpawnNewInstance } + + - { key: Paste, action: Paste } + - { key: Copy, action: Copy } + - { key: L, mods: Control, action: ClearLogNotice } + - { key: L, mods: Control, chars: "\x0c" } + - { key: PageUp, mods: Alt, action: ScrollPageUp } + - { key: PageDown, mods: Alt, action: ScrollPageDown } diff --git a/.config/alacritty/launcher.yml b/.config/alacritty/launcher.yml new file mode 100644 index 0000000..70f3c45 --- /dev/null +++ b/.config/alacritty/launcher.yml @@ -0,0 +1,55 @@ +env: + TERM: alacritty + GUICALL: '1' + +window: + padding: + x: 8 + y: 8 + dynamic_padding: true + decorations: none + +font: + normal: + family: Iosevka + style: Medium + bold: + style: Bold + italic: + style: Italic + size: 13.0 + +colors: + primary: + background: '#221e1d' + foreground: '#fffefe' + dim_foreground: '#BDC0BA' + bright_foreground: '#ffffff' # just white ;) + cursor: + text: '#221e1d' + cursor: '#fffefe' + normal: + black: '#221e1d' + red: '#b76868' + green: '#bfbd90' + yellow: '#ae9892' + blue: '#b5c1c0' + magenta: '#76a3b0' + cyan: '#4777aa' + white: '#c7c6c6' + bright: + black: '#3d3533' + red: '#ff7474' + green: '#ffffa5' + yellow: '#f6b9a9' + blue: '#d3ffff' + magenta: '#86def7' + cyan: '#4c9aec' + white: '#fffefe' + +background_opacity: 0.95 +# shell: +# program: /usr/bin/env +# args: +# - LD_LIBRARY_PATH=/usr/local/lib64 +# - mrsh diff --git a/.config/evdoublebind/config b/.config/evdoublebind/config new file mode 100644 index 0000000..b5feede --- /dev/null +++ b/.config/evdoublebind/config @@ -0,0 +1,3 @@ +unused : <ESC> +kbd: /dev/input/by-path/platform-i8042-serio-0-event-kbd +<CAPS> : void | void diff --git a/.config/git/config b/.config/git/config new file mode 100644 index 0000000..a689381 --- /dev/null +++ b/.config/git/config @@ -0,0 +1,219 @@ +[user] + name = Robert Günzler + email = r@gnzler.io + +[sendemail] + smtpserver = /usr/bin/msmtp + smtpserveroption=-a + smtpserveroption=gnzler + annotate = true + confirm = always + +[core] + excludesfile = ~/.git/ignore + # Treat spaces before tabs and all kinds of trailing whitespace as an error + # [default] trailing-space: looks for spaces at the end of a line + # [default] space-before-tab: looks for spaces before tabs at the beginning of a line + whitespace = space-before-tab,-indent-with-non-tab,trailing-space + + # Make `git rebase` safer on OS X + # More info: <http://www.git-tower.com/blog/make-git-rebase-safe-on-osx/> + trustctime = false + + # Prevent showing files whose names contain non-ASCII symbols as unversioned. + # http://michael-kuehnel.de/git/2014/11/21/git-mac-osx-and-german-umlaute.html + precomposeunicode = false + filemode = false + +[init] + # templateDir = ~/.dotfiles/git/templates + +[commit] + # template = ~/.gitmessage + +[alias] + stp = status --porcelain --branch + sti = status --porcelain --branch --ignored + st = status -sb + stl = ls-files -m -o --exclude-standard + ci = commit + br = branch + co = checkout + cp = cherry-pick + cr = clone --recursive + df = diff --word-diff + d = "!f() { local a=${1:-HEAD}; local b=${2:-master}; if [[ $a == '-' ]]; then a=HEAD; fi; printf ' %s is %s commits ahead and %s commits behind %s\n' `git rev-parse --short=7 $a` `git rev-list --count $b..$a` `git rev-list --count $a..$b` $b; printf ' last common commit (merge-base): %s\n' `git merge-base $a $b`; git diff --shortstat $a..$b; }; f" + unstage = reset --hard HEAD + l = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit + ll = log --pretty=oneline --graph --abbrev-commit + lm = log --pretty=format:'* %s (%h)' + lg = log -p + g = grep --break --heading --line-number + amend = commit --amend --reuse-message=HEAD --edit + contrib = shortlog --summary --numbered + show-ignored = "! git clean -ndX | perl -pe 's/Would remove/Ignored:/'" + # usage: change-date [rev] [date] [...additional filter-branch flags] + # rewrites the commit/author date of <rev> to be <date> + # if <rev> is empty HEAD is used + # if <date> is empty the current time is used + change-date = "!f() { now=\"$(date +'%a %b %d %H:%M:%S %Y %z')\"; head=\"$(git rev-parse HEAD)\"; git filter-branch $3 --env-filter 'if [[ $GIT_COMMIT = ${1-$head} ]]; then export GIT_AUTHOR_DATE=${2:-$now}; export GIT_COMMITTER_DATE=${2:-$now}; fi'; }; f" + # usage: ld <n> + # prints out the `git log` for the n last days + log-since = "!sh -c \"git log --since '${1:-1} days ago' --oneline --author $(git config user.email)\" -" + # usage: pr <num> [<remote>] + # creates local branch pr/<num> from origin or <remote> and checks it out + pr = "!f() { git fetch -fu ${2:-origin} refs/pull/$1/head:pr/$1 && git checkout pr/$1; }; f" + total-lines = "! git ls-tree -r master --name-only | xargs -I {} git blame --line-porcelain {} | sed -n 's/^author //p' | sort | uniq -c | sort -rn" + fixup = "!f() { git commit --fixup=${1}; }; f" + rbi = rebase --autosquash -i + rbi-pr = !bash -c 'git rebase --autosquash -i `git merge-base HEAD ${1:-origin/master}`' + pushf = push --force-with-lease + root = "rev-parse --show-toplevel" + trailer = "!f() { msg=$(git rev-parse --show-toplevel)/LAST_COMMIT_MSG; git log -1 --format=%B > $msg; git interpret-trailers --in-place --trailer $@ $msg; git commit --allow-empty --no-verify --amend --only --edit --file $msg; rm $msg; }; f" + a = add --patch + edit = !bash -c 'set -x && git rbi "${1:-HEAD}"^' - + oldest-ancestor = !bash -c 'diff -u <(git rev-list --first-parent ${1:-master}) <(git rev-list --first-parent ${2:-HEAD}) | sed -ne \"s/^ //p\" | head -1' + rebase-onto = !bash -c 'git rebase --onto ${1} `git oldest-ancestor ${2} ${3}` ${3}' - + find-by-subject = "!f() { set -ex; s=`git log -1 --format=%s ${1}`; git --no-pager log -1 --grep=\"^${s}$\" --no-merges ${2}; }; f" + tig = !tig + + +[pager] + diff = less + +[apply] + # Detect whitespace errors when applying a patch + whitespace = fix + +[color] + # Use colors in Git commands that are capable of colored output when + # outputting to the terminal. (This is the default setting in Git ≥ 1.8.4.) + ui = auto + +[color "branch"] + current = yellow reverse + local = yellow + remote = green + +[color "diff"] + meta = yellow bold + frag = magenta bold # line info + old = red # deletions + new = green # additions + +[color "status"] + added = yellow + changed = green + untracked = cyan + +[credential] + # helper = osxkeychain + +[diff] + # Detect copies as well as renames + renames = copies + renameLimit = 3210 + +[diff "bin"] + # Use `hexdump` to diff binary files + textconv = hexdump -v -C + renames = copies + tool = vimdiff +[difftool] + prompt = false + +[fetch] + prune = true + +[grep] + lineNumber = true + +[help] + # Automatically correct and execute mistyped commands + autocorrect = 10 + +[merge] + # Include summaries of merged commits in newly created merge commit messages + log = true + defaultToUpstream = true + tool = vimdiff +[mergetool "vimdiff"] + path = /usr/bin/nvim + conflictstyle = diff3 + prompt = false + +[rebase] + ; instructionFormat = "%aI %an <%ae> | %s" + +[rerere] + enabled = false + +[push] + # Use the Git 1.x.x default to avoid errors on machines with old Git + # installations. To use `simple` instead, add this to your `~/.extra` file: + # `git config --global push.default simple`. See http://git.io/mMah-w. + default = simple + # Make `git push` push relevant annotated tags when pushing branches out. + followTags = true + +# URL shorthands +[url "git@github.com:"] + insteadOf = "gh:" + insteadOf = "https://github.com/" + pushInsteadOf = "github:" + pushInsteadOf = "git://github.com/" + +[url "git@bitbucket.org:"] + insteadOf = "bb:" + insteadOf = "https://bitbucket.org/" + pushInsteadOf = "bitbucket:" + pushInsteadOf = "git://bitbucket.org/" + +[url "git@gitlab.com:"] + insteadOf = "gl:" + insteadOf = "https://gitlab.com/" + pushInsteadOf = "gitlab:" + pushInsteadOf = "git://gitlab.com/" + +[url "git@git.sr.ht:"] + insteadOf = "srht:" + insteadOf = "https://git.sr.ht/" + pushInsteadOf = "srht:" + pushInsteadOf = "git://git.sr.ht/" + +[url "git://github.com/"] + insteadOf = "github:" + +[url "git@gist.github.com:"] + insteadOf = "gst:" + insteadOf = "gist:" + pushInsteadOf = "gist:" + pushInsteadOf = "git://gist.github.com/" + +[http] + sslverify = true + cookiefile = /home/robert/.gitcookies + +[trailer "sign"] + key = "Signed-off-by" + ifmissing = add + ; command = bash -c 'echo \"$(git config user.name) <${$(git config user.signemail):-$(git config user.email)}>\"' + command = echo \"`git config user.name` <`git config user.signemail || git config user.email`>\" + where = end +[trailer "changetype"] + key = "Change-type" + ifmissing = add + ifexists = doNothing + where = start +[trailer "connects"] + key = "Connects-to" + ifmissing = add + where = start +[trailer "depends"] + key = "Depends-on" + ifmissing = add + where = start +[trailer "changelog"] + key = "Changelog-entry" + ifmissing = add + where = start diff --git a/.config/git/ignore b/.config/git/ignore new file mode 100644 index 0000000..9a4c0d2 --- /dev/null +++ b/.config/git/ignore @@ -0,0 +1,49 @@ +# Random +*~ +._* +Thumbs.db +.DS_Store* +Desktop.ini +.idea +ehthumbs.db +Icon* +Session.vim + +# external +.Spotlight-V100 +.Trashes + +# compiled +*.o +*.out +*.pyc +*.so +*.exe +*.class +*.dll +bin/ +tags + +# logs and db +*.log +*.sql +*.sqlite +*.db + +# packages +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# web stuff +node_modules +bower_components + +# personal stuff +.todo.md +.org.md diff --git a/.config/imv/config b/.config/imv/config new file mode 100644 index 0000000..64b2d1f --- /dev/null +++ b/.config/imv/config @@ -0,0 +1,10 @@ +[options] +overlay = true +overlay_font = Iosevka Sparkle:14 + +[binds] +n = next +p = prev +e = exec echo $imv_current_file +u = scaling none +y = scaling full diff --git a/.config/kanshi/config b/.config/kanshi/config new file mode 100644 index 0000000..2688b66 --- /dev/null +++ b/.config/kanshi/config @@ -0,0 +1,13 @@ +laptop{ + output eDP-1 mode 2560x1440 scale 1 position 0,0 enable +} + +desk{ + output "Dell Inc. DELL U2518D 3C4YP87AAK3L" mode 2560x1440 position 0,0 enable + output eDP-1 mode 2560x1440 scale 1 position 2560,0 enable +} + +tv{ + output HDMI-A-1 position 2560,0 enable + output eDP-1 mode 2560x1440 scale 1 position 0,0 enable +} diff --git a/.config/mako/config b/.config/mako/config new file mode 100644 index 0000000..567c2ba --- /dev/null +++ b/.config/mako/config @@ -0,0 +1,24 @@ +# vim: ft=config + +layer=top +anchor=top-right + +# font=Fira Sans +font=Iosevka Sparkle +padding=10 +width=500 +height=300 +background-color=#1C1C1Cee +text-color=#FCFAF2ff +border-color=#0B346Edd +border-radius=3 +progress-color=source #0B346Edd + +group-by=app-name +default-timeout=3000 + +[urgency=high] +border-color=#db4d6ddd + +[app-name="kp-cli"] +border-color=#26a65bdd diff --git a/.config/mpv/config b/.config/mpv/config new file mode 100644 index 0000000..23e9637 --- /dev/null +++ b/.config/mpv/config @@ -0,0 +1,97 @@ +# MPV Configuration +# clean up input config (helps with iina, gnome-mpv etc.) +# no-input-default-bindings +autoload-files=no +input-ipc-server=/var/run/user/1000/mpv + +# +# Video +# +framedrop=vo +video-sync=display-resample +gpu-context=wayland + +# +# Audio +# +alang=jpn,ja,eng,en,ger,de +volume-max=200 +audio-channels=auto-safe +# bs2b filter +# af-add=lavfi=[bs2b=profile=jmeier] #default,cmoy + +# +# Subtitles +# +slang=eng,en,ger,de +sub-font="Rosario" +sub-bold=yes +sub-ass-force-style=Kerning=yes + +# +# OSD +# +#osc=no +keep-open=yes +stop-screensaver=yes +cursor-autohide-fs-only +osd-font="Iosevka Sparkle" +# osd-font=monospace +osd-font-size=14 +osd-scale-by-window=yes +osd-fractions=yes +geometry=50%:50% +hidpi-window-scale +load-stats-overlay=yes +load-osd-console=yes + +# +# Misc. +# +screenshot-directory=~/Pictures/screenshots/mpv +screenshot-template='%tY%tm%td-%tH%tM%tS-%F-%p' +# screenshot-format=png +# screenshot-jpeg-quality=100 +# user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:42.0) Gecko/20100101 Firefox/42.0" +# ytdl-format=bestvideo+bestaudio,best + +# +# PROFILES +# + +[anime] +profile-desc="Optimized for watching anime with ASS subtitles" +sub-auto=fuzzy +# deband=yes +# sub-border-size=2 +# sub-font-size=45 +# sub-margin-y=50 +# sub-spacing=1 + +[cache] +profile-desc="Optimized for streaming web video" +cache=yes +cache-secs=36000 +script=~/.config/mpv/scripts/mpv-plugin-streamcache/streamcache.lua +ytdl-format='bestvideo[ext=mp4]+bestaudio' + +[3d] +profile-desc="Convert 3d to 2d" +hwdec=no +vf-add=stereo3d=sbs2l:mono + +[webcam] +profile-desc="Optimized for showing internal webcam feed" +osc=no +profile=low-latency +video-sync=display-desync # needed or external audio will fail +pulse-latency-hacks=yes +demuxer-lavf-o=input_format=mjpeg,video_size=848x480 +framedrop=no +# no-demuxer-thread +opengl-glfinish=yes +opengl-swapinterval=0 +vf=hflip + +[sharp] +glsl-shaders="~/.config/mpv/shaders/finesharp/FineSharp.glsl" diff --git a/.config/mpv/input.conf b/.config/mpv/input.conf new file mode 100644 index 0000000..3323ca0 --- /dev/null +++ b/.config/mpv/input.conf @@ -0,0 +1,101 @@ +# MPV INPUT CONFIG + +# taken from the default config +ESC set fullscreen no +f cycle fullscreen +SPACE cycle pause +m cycle mute + +# ARROW KEY BINDINGS +RIGHT seek 10 # skip ahead +LEFT seek -10 # skip back +UP seek 60 +DOWN seek -60 + +HOME seek 0 absolute-percent +END seek 100 absolute-percent + +Shift+RIGHT playlist-next +Shift+LEFT playlist-prev +Alt+RIGHT frame-step +Alt+LEFT frame-back-step + +Shift+UP add volume 10 +Shift+DOWN add volume -10 +Alt+UP add volume 5 +Alt+DOWN add volume -5 + +PGUP add chapter 1 # Fn-UP; skip to next chapter +PGDWN add chapter -1 # Fn-DOWN; skip to previous chapter +# Meta+UP add chapter 1 +# Meta+DOWN add chapter -1 +Shift+PGUP seek 600 +Shift+PGDWN seek -600 +Alt+PGUP add sub-pos -1 +Alt+PGDWN add sub-pos +1 + +C script_binding autocrop # crop black bars from movies (should be activated on scenes with max. contrast to the black bars) +Ctrl+c script_binding slicing/slicing_mark +Ctrl+a script_binding slicing/slicing_audio +W ignore +Ctrl+W script_binding webm/display-webm-encoder + +# A/V/S BINDINGS +a cycle audio +v cycle video +Y cycle-values vf stereo3d=sbs2l:mono stereo3d=ab2l:mono "" # cycle methods to display 3d movie as 2d +D cycle deinterlace + +s cycle sub +S cycle sub down +e add sub-scale +0.1 # increase subtitle font size +w add sub-scale -0.1 # decrease subtitle font size +t add sub-pos -1 +r add sub-pos +1 + +z add sub-delay -0.1 +Z add audio-delay -0.100 +x add sub-delay +0.1 +X add audio-delay 0.100 + +u cycle-values sub-ass-override "force" "strip" "no" +U cycle sub-visibility +Meta+s cycle sub-ass-force-margins + +# PLAYER BINDINGS +O cycle osc +i script-binding stats/display-stats-toggle +g screenshot # take a screenshot +G screenshot video # ...without subtitles + +T cycle ontop +H cycle-values hwdec auto no +A cycle-values video-aspect "16:9" "4:3" "21:9" "2.35:1" "-1" +c cycle-values audio-channels "auto-safe" "auto" "stereo" + +q quit +Q quit-watch-later # saves playback position to resume later + += add volume 10 +Shift+= add volume 5 +- add volume -10 +Shift+- add volume -5 + +> frame-step +< frame-back-step + +# VAPOURSYNTH/FILTER BINDINGS +# banding +b cycle deband +B vf toggle gradfun +# interpolation +P vf toggle format=yuv420p,vapoursynth=~/.dotfiles/scripts/motioninterpolation.vpy:4:4 +# binaural correction +h cycle-values af lavfi=[bs2b=profile=default] lavfi=[bs2b=profile=jmeier] lavfi=[bs2b=profile=cmoy] lavfi=[earwax] "" + +WHEEL_UP seek -5 +WHEEL_DOWN seek 5 + +0 script_binding mouse_coords +# 8 script_binding mouse_coords/set +# 9 script_binding mouse_coords/measure diff --git a/.config/mpv/script-opts/console.conf b/.config/mpv/script-opts/console.conf new file mode 100644 index 0000000..26581d1 --- /dev/null +++ b/.config/mpv/script-opts/console.conf @@ -0,0 +1,2 @@ +font="Iosevka Sparkle" +font_size=18 diff --git a/.config/mpv/script-opts/osc.conf b/.config/mpv/script-opts/osc.conf new file mode 100644 index 0000000..c99b867 --- /dev/null +++ b/.config/mpv/script-opts/osc.conf @@ -0,0 +1,23 @@ +# +# MPV LUA SETTINGS +# + +layout=bottombar +# layout=topbar +# layout=slimbox +# layout=box + +boxalpha=90 +# valign=-1 +# hidetimeout=1000 +# fadeduration=100 +seekbarstyle=bar +# seekbarstyle=slider +# seekbarstyle=knob + +# use native mpv values and disable OSC internal track list management (and some functions that depend on it) +iamaprogrammer=yes +# display total time instead of remaining time? +# timetotal=no +# display timecodes with milliseconds +# timems=yes diff --git a/.config/mpv/script-opts/stats.conf b/.config/mpv/script-opts/stats.conf new file mode 100644 index 0000000..d9f699f --- /dev/null +++ b/.config/mpv/script-opts/stats.conf @@ -0,0 +1,4 @@ +font="Iosevka Sparkle" +font_mono="Iosevka Sparkle" +font_size=8 +border_size=0.6 diff --git a/.config/mpv/scripts/mouse_coords.lua b/.config/mpv/scripts/mouse_coords.lua new file mode 100644 index 0000000..dae6239 --- /dev/null +++ b/.config/mpv/scripts/mouse_coords.lua @@ -0,0 +1,31 @@ +local msg = require('mp.msg') + +-- local x, y = 0, 0 + +-- function set_marker() +-- x, y = mp.get_mouse_pos() +-- end + +-- function measure() +-- x2, y2 = mp.get_mouse_pos() +-- w = x2-x +-- h = y2-y + +-- pos = "x: " .. x .. ", y:" .. y +-- dimensions = "w: " .. h .. ", h:" .. h +-- output = pos .. " | " .. dimensions + +-- mp.msg.warn(output) +-- mp.osd_message(output) +-- end + +function print_pos() + x, y = mp.get_mouse_pos() + pos = "x: " .. x .. ", y: " .. y + mp.msg.warn(pos) + mp.osd_message(pos) +end + +mp.add_key_binding("0", mp.get_script_name(), print_pos) +-- mp.add_key_binding("8", mp.get_script_name().."/set", set_marker) +-- mp.add_key_binding("9", mp.get_script_name().."/measure", measure) diff --git a/.config/sway/auto.conf b/.config/sway/auto.conf new file mode 100644 index 0000000..725900c --- /dev/null +++ b/.config/sway/auto.conf @@ -0,0 +1,60 @@ +# vim: ft=i3 +# NOTE: never use assign, it crashes sway for some reason... + + +for_window [class="nnn"] border pixel +for_window [app_id="mpv"] floating enable; border pixel; inhibit_idle open +for_window [app_id="Alacritty"] border pixel +for_window [app_id="^__floating-.*"] floating enable +for_window [class="pinentry"] floating enable; border normal +for_window [class="zoom" title="Zoom - Free Account"] floating enable +for_window [class="zoom" title="Settings"] floating enable +for_window [class="zoom"] move workspace 9; move workspace to output eDP-1 +for_window [title="tridactyledit"] floating enable +for_window [class="feh"] floating enable; urgent enable +for_window [class="Sxiv"] floating enable; urgent enable +for_window [title="Volume Control"] floating enable +for_window [title="Calculator"] floating enable +for_window [title="wpgtk 6.0.1"] floating enable; border normal +for_window [app_id="launcher"] floating enable; border pixel +for_window [app_id="nm-connection-editor"] floating enable; border normal +for_window [class="balena-etcher-electron"] floating enable; border normal +for_window [app_id="imv"] border pixel +for_window [app_id="firefox" title="Picture-in-Picture"] \ + floating enable, sticky enable, border none + +# Tag windows +for_window [class="zoom"] mark work + +for_window [app_id="firefox"] mark browser +for_window [class="Nightly"] mark browser +for_window [class="Chromium-browser"] mark work; mark browser +for_window [class="Mail"] mark mail +for_window [class="Thunderbird"] mark mail +for_window [class="discord"] mark mail +for_window [class="TelegramDesktop"] mark mail +for_window [app_id="firefox" title="^web.whatsapp.com"] mark mail + +for_window [con_mark="browser"] \ + border pixel, inhibit_idle fullscreen + +# Move around + +set $outputSide eDP-1 +set $wsWork 3 +set $wsMail 2 +for_window [con_mark="work"] move workspace $wsWork +for_window [con_mark="mail"] move workspace $wsMail, move workspace to output $outputSide, layout tabbed + +for_window [class="Spotify"] \ + move workspace $wsMusic, move workspace to output $outputSide, layout tabbed, inhibit_idle open + +for_window [app_id="spotify-tui"] \ + move workspace $wsMusic, move workspace to output $outputSide, layout tabbed, inhibit_idle open + +# for_window [class="osu!"] \ +# move workspace $wsGame, layout tabbed +# for_window [class="^.inecraft.*"] \ +# move workspace $wsGame, layout tabbed + +for_window [class="KeePassXC"] move scratchpad; scratchpad show diff --git a/.config/sway/binds.conf b/.config/sway/binds.conf new file mode 100644 index 0000000..4ee9ce0 --- /dev/null +++ b/.config/sway/binds.conf @@ -0,0 +1,215 @@ +# vim: ft=i3 + +# Basics {{{ +# start a terminal +bindsym $mod+Return exec $term +bindsym $mod+Shift+Return exec $floating_term + +# kill focused window +bindsym $mod+Shift+q kill + +# start your launcher +bindsym $mod+d exec $menu +bindsym $mod+o exec $run_menu + +# Drag floating windows by holding down $mod and left mouse button. +# Resize them with right mouse button + $mod. +# Despite the name, also works for non-floating windows. +# Change normal to inverse to use left mouse button for resizing and right +# mouse button for dragging. +floating_modifier $mod normal + +# reload the configuration file +bindsym $mod+Shift+c reload + +# exit sway (logs you out of your wayland session) +bindsym $mod+Shift+e exit +bindsym $mod+Shift+p exec $HOME/.dotfiles/bin/lock + +bindsym $mod+Shift+s mode "screengrab" +mode "screengrab" { +bindsym f exec grimshot save screen; mode "default" +bindsym s exec grimshot save area; mode "default" +bindsym c exec grimshot copy area; mode "default" +bindsym r exec grimshot rec screen; mode "default" +bindsym p exec colorpick; mode "default" +bindsym Shift+r exec grimshot rec area; mode "default" +bindsym Escape mode "default" +} + +bindsym $mod+Shift+b border toggle + +bindsym $mod+a exec soundswitch_dmenu +bindsym $mod+Shift+a exec soundswitch_dmenu global +bindsym $mod+n exec networkmanager_dmenu +# }}} + +# Moving around {{{ +# Move your focus around +bindsym $mod+$left focus left +bindsym $mod+$down focus down +bindsym $mod+$up focus up +bindsym $mod+$right focus right +# or use $mod+[up|down|left|right] + +# _move_ the focused window with the same, but add Shift +bindsym $mod+Shift+$left move left +bindsym $mod+Shift+$down move down +bindsym $mod+Shift+$up move up +bindsym $mod+Shift+$right move right + +bindsym $mod+Next focus child +bindsym $mod+Prior focus parent + +bindsym $mod+Escape [urgent="latest"] focus +# }}} + +# Workspaces {{{ +# Note: workspaces can have any name you want, not just numbers. +# We just use 1-10 as the default. +bindsym $mod+Shift+comma workspace prev +bindsym $mod+Shift+period workspace next +# switch to workspace +bindsym $mod+1 workspace $ws1 +bindsym $mod+2 workspace $ws2 +bindsym $mod+3 workspace $ws3 +bindsym $mod+4 workspace $ws4 +bindsym $mod+5 workspace $ws5 +bindsym $mod+6 workspace $ws6 +bindsym $mod+7 workspace $ws7 +bindsym $mod+8 workspace $ws8 +bindsym $mod+9 workspace $ws9 + +bindsym $mod+Ctrl+m workspace $wsMusic +bindsym $mod+Ctrl+v workspace $wsVideo +bindsym $mod+Ctrl+g workspace $wsGame +bindsym $mod+Tab workspace back_and_forth + +# move focused container to workspace +bindsym $mod+Shift+1 move container to workspace $ws1 +bindsym $mod+Shift+2 move container to workspace $ws2 +bindsym $mod+Shift+3 move container to workspace $ws3 +bindsym $mod+Shift+4 move container to workspace $ws4 +bindsym $mod+Shift+5 move container to workspace $ws5 +bindsym $mod+Shift+6 move container to workspace $ws6 +bindsym $mod+Shift+7 move container to workspace $ws7 +bindsym $mod+Shift+8 move container to workspace $ws8 +bindsym $mod+Shift+9 move container to workspace $ws9 + +# move focused workspce to output +bindsym $mod+Ctrl+$left move workspace to output left +bindsym $mod+Ctrl+$down move workspace to output down +bindsym $mod+Ctrl+$up move workspace to output up +bindsym $mod+Ctrl+$right move workspace to output right +# }}} + +# Layout stuff {{{ +bindsym $mod+Alt+v splith +bindsym $mod+Alt+h splitv + +# Make the current focus fullscreen +bindsym $mod+f fullscreen +# Hide the topbar (also gets you more space) +bindsym $mod+Shift+t bar mode toggle topbar; default_border pixel + +# Swap focus between the tiling area and the floating area +bindsym $mod+space focus mode_toggle +# Toggle the current focus between tiling and floating mode +bindsym $mod+Ctrl+space floating toggle + +mode "layout" { + # Switch the current container between different layout styles + bindsym s layout stacking; mode "default" + bindsym w layout tabbed; mode "default" + bindsym e layout toggle split; mode "default" + + # move focus to the parent container + bindsym p focus parent + bindsym Shift+p focus child + + # make a sticky container + bindsym t sticky toggle; mode "default"; exec notify-send "container sticky'd" + + # return to default mode + bindsym Return mode "default" + bindsym Escape mode "default" +} +bindsym $mod+y mode "layout" +# }}} + +# Scratchpad {{{ +# Sway has a "scratchpad", which is a bag of holding for windows. +# You can send windows there and get them back later. + +# Move the currently focused window to the scratchpad +bindsym $mod+Ctrl+backspace move scratchpad + +# Show the next scratchpad window or hide the focused scratchpad window. +# If there are multiple scratchpad windows, this command cycles through them. +bindsym $mod+backspace scratchpad show +# }}} + +# Resizing containers {{{ +mode "resize" { + # 1x + set $resizeRate1x 50 + bindsym $left resize shrink width $resizeRate1x px + bindsym $down resize grow height $resizeRate1x px + bindsym $up resize shrink height $resizeRate1x px + bindsym $right resize grow width $resizeRate1x px + bindsym Shift+$down resize shrink width $resizeRate1x px ; resize shrink height $resizeRate1x px + bindsym Shift+$up resize grow width $resizeRate1x px ; resize grow height $resizeRate1x px + + # 2x + set $resizeRate2x 100 + bindsym Left resize shrink width $resizeRate1x px + bindsym Down resize grow height $resizeRate1x px + bindsym Up resize shrink height $resizeRate1x px + bindsym Right resize grow width $resizeRate1x px + bindsym Shift+Down resize shrink width $resizeRate2x px ; resize shrink height $resizeRate2x px + bindsym Shift+Up resize grow width $resizeRate2x px ; resize grow height $resizeRate2x px + + # return to default mode + bindsym Return mode "default" + bindsym Escape mode "default" +} +bindsym $mod+r mode "resize" +# }}} + +# Gaps {{{ +mode "gaps" { + bindsym r gaps outer all set 0; gaps inner all set 0 + + set $gapRate 5 + bindsym $up gaps inner all plus $gapRate + bindsym $down gaps inner all minus $gapRate + + # return to default mode + bindsym Return mode "default" + bindsym Escape mode "default" +} +bindsym $mod+Alt+g mode "gaps" +# }}} + +# Misc {{{ +bindsym $mod+Shift+x mark --toggle "x" +bindsym $mod+x [con_mark="x"] focus +bindsym $mod+Ctrl+x swap container with mark "x" + +# Media volume controls +bindsym XF86AudioMute exec pactl set-sink-mute '@DEFAULT_SINK@' toggle +bindsym XF86AudioMicMute exec pactl set-input-mute '@DEFAULT_INPUT@' toggle +bindsym XF86AudioLowerVolume exec pactl set-sink-volume '@DEFAULT_SINK@' -5% +bindsym XF86AudioRaiseVolume exec pactl set-sink-volume '@DEFAULT_SINK@' +5% + +# Sreen brightness controls +bindsym XF86MonBrightnessUp exec light -A 5 +bindsym XF86MonBrightnessDown exec light -U 5 + +# Media player controls +bindsym XF86AudioPlay exec playerctl play-pause +# bindsym XF86AudioForward exec playerctl next +# bindsym XF86AudioRewind exec playerctl previous +bindsym XF86AudioNext exec playerctl next +bindsym XF86AudioPrev exec playerctl previous +# }}} diff --git a/.config/sway/config b/.config/sway/config new file mode 100644 index 0000000..97424b1 --- /dev/null +++ b/.config/sway/config @@ -0,0 +1,71 @@ +# vim: ft=i3 fdm=marker + +set $mod Mod4 +set $alt Mod1 + +set $left h +set $down j +set $up k +set $right l + +set $term alacritty +set $floating_term $term --config-file $HOME/.config/alacritty/launcher.yml --class '__floating-Alacritty' +set $menu wofi -k /dev/null --terminal $term --show drun +set $run_menu menu -run + +# workspaces {{{ +set $ws1 1 +set $ws2 2 +set $ws3 3 +set $ws4 4 +set $ws5 5 +set $ws6 6 +set $ws7 7 +set $ws8 8 +set $ws9 9 +set $wsMusic m +set $wsVideo v +set $wsGame g +# }}} + +# gaps inner 5 +show_marks yes + +# default keyboard +input * { + xkb_layout us + xkb_variant mac + xkb_options 'caps:hyper,lvl3:ralt_switch_multikey' + repeat_rate 70 +} + +# default pointer +input * { + accel_profile adaptive +} + +# TODO +include input.conf +include binds.conf +include auto.conf + +# style {{{ +set $text #FCFAF2 +set $text2 #BDC0BA +set $bg #1C1C1C +set $bg2 #434343 +set $main #0B346F +set $main2 #005CAF +set $urgent #DB4D6D +set $urgent2 #CB1B45 +set $indicator #FAD689 +set $indicator2 #C18A26 + +client.focused $main2 $main $text $indicator $main +client.focused_inactive $bg2 $bg $text2 $indicator2 $bg +client.unfocused $bg2 $bg $text2 $indicator2 $bg +client.urgent $urgent2 $urgent $text $indicator $urgent +# }}} + +include config.d/* +include "`uname -n`" diff --git a/.config/sway/contrib/swayidle.confd b/.config/sway/contrib/swayidle.confd new file mode 100644 index 0000000..405cdbe --- /dev/null +++ b/.config/sway/contrib/swayidle.confd @@ -0,0 +1,8 @@ + +SWAYIDLE_COMMAND=<<EOF +timeout 540 'notify-send swayidle "screen off in 60s"' +timeout 600 'sway output * dpms off' + resume 'sway output * dpms on' +timeout 800 suspend +before-sleep lock +EOF diff --git a/.config/sway/contrib/swayidle.initd b/.config/sway/contrib/swayidle.initd new file mode 100755 index 0000000..169e5cf --- /dev/null +++ b/.config/sway/contrib/swayidle.initd @@ -0,0 +1,6 @@ +#!/sbin/openrc-run + +command=swayidle +command_args="${SWAYIDLE_COMMAND}" + +pidfile=/var/run/swayidle.pid diff --git a/.config/sway/env b/.config/sway/env new file mode 100644 index 0000000..c7eb252 --- /dev/null +++ b/.config/sway/env @@ -0,0 +1,22 @@ +#!/bin/sh + +# export XDG_RUNTIME_DIR=/run/user/1000 + +export DESKTOP_SESSION=gnome +export GDK_BACKEND=wayland +export CLUTTER_BACKEND=wayland + +# export QT_QPA_PLATFORM=wayland-egl +export QT_QPA_PLATFORM= +# export QT_AUTO_SCREEN_SCALE_FACTOR=0 +# export QT_SCREEN_SCALE_FACTORS='1.5;1' +export QT_QPA_PLATFORMTHEME=qgnomeplatform +export QT_STYLE_OVERRIDE=adwaita + +export _JAVA_AWT_WM_NONREPARENTING=1 + +export XMODIFIERS=@im=ibus +export QT_IM_MODULE=ibus +export GTK_IM_MODULE=xim + +export TERM=alacritty diff --git a/.config/sway/hibiki b/.config/sway/hibiki new file mode 100644 index 0000000..033ba39 --- /dev/null +++ b/.config/sway/hibiki @@ -0,0 +1,39 @@ +# vim: ft=i3 + +output * scale 1 +output * bg $HOME/.background fill + +font pango:Iosevka Sparkle 12 + +output * max_render_time 1 +# for_window [app_id=Alacritty] max_render_time 2 +for_window [app_id=firefox] max_render_time 3 + +input "1:1:AT_Translated_Set_2_keyboard" { + xkb_options 'caps:hyper,lvl3:ralt_switch_multikey,grp:alt_space_toggle,eurosign:5' +} +input "2:7:SynPS/2_Synaptics_TouchPad" { + natural_scroll enabled + dwt enabled + tap enabled + click_method clickfinger + scroll_method two_finger + # pointer_accel 1 +} +input "2:10:TPPS/2_Elan_TrackPoint" { + dwt enabled + natural_scroll enabled +} + +bar { + swaybar_command waybar +} + +exec mako +exec kanshi +exec swayidle \ + timeout 540 'notify-send swayidle "screen off in 60s"' \ + timeout 600 'sway output * dpms off' \ + resume 'sway output * dpms on' \ + timeout 800 suspend \ + before-sleep lock diff --git a/.config/sway/input.conf b/.config/sway/input.conf new file mode 100644 index 0000000..42b959b --- /dev/null +++ b/.config/sway/input.conf @@ -0,0 +1,35 @@ +# vim: ft=i3 + +# input club kbd +input "65261:25700:Input_Club_Infinity_keyboard/QMK" { + # intentionally left blank +} + +# planck ez +input "65261:24672:OLKB_Planck" { + xkb_options 'eurosign:5,lvl3:ralt_switch_multikey' +} + +# trackball +input "1149:8264:Primax_Kensington_Eagle_Trackball" { + # intentionally left blank +} + +# pen + tablet +input "10429:148:XP-PEN_STAR_G640_Pen" { + click_method clickfinger + middle_emulation disabled + accel_profile flat + drag enabled + tap enabled + + # 331 lower pen button + # 332 upper pen button + scroll_method on_button_down + scroll_button 274 + + map_to_output HDMI-A-1 + map_from_region 0.0x0.0 0.3x0.3 +} +input "10429:148:XP-PEN_STAR_G640_Mouse" events disabled +input "10429:148:XP-PEN_STAR_G640_Keyboard" events disabled diff --git a/.config/sway/simple.conf b/.config/sway/simple.conf new file mode 100644 index 0000000..435f235 --- /dev/null +++ b/.config/sway/simple.conf @@ -0,0 +1,26 @@ +set $mod Mod4 +set $left h +set $right l +set $up k +set $down j +set $term alacritty +set $menu wofi -terminal $term -show drun + +bindsym $mod+Return exec $term +bindsym $mod+Shift+q kill +bindsym $mod+d exec $menu +bindsym $mod+Shift+c reload +bindsym $mod+Shift+e exit +bindsym $mod+$left focus left +bindsym $mod+$down focus down +bindsym $mod+$up focus up +bindsym $mod+$right focus right +bindsym $mod+Shift+$left move left +bindsym $mod+Shift+$down move down +bindsym $mod+Shift+$up move up +bindsym $mod+Shift+$right move right +bindsym $mod+f fullscreen +bindsym $mod+Backspace seat - pointer_constraint disable +bindsym $mod+0 seat - pointer_constraint escape + +exec swaynag -m $(sway --get-socketpath) diff --git a/.config/tridactyl/themes/r.css b/.config/tridactyl/themes/r.css new file mode 100644 index 0000000..9017468 --- /dev/null +++ b/.config/tridactyl/themes/r.css @@ -0,0 +1,59 @@ +:root.TridactylThemeR { + --tridactyl-bg: #1c1c1c; + --tridactyl-bg-second: #38383d; + --tridactyl-fg: #f9f9fa; + --tridactyl-of-bg: #005CAF; + /* --tridactyl-of-fg: ; */ + /* --tridactyl-hightlight-box-bg: ; */ + /* --tridactyl-hightlight-box-fg: ; */ + --tridactyl-font-family: "Iosevka Sparkle", monospace; + --tridactyl-font-family-sans: sans-serif; + --tridactyl-font-size: 12pt !important; + --tridactyl-small-font-size: 12pt; + --tridactyl-cmdl-bg: #1c1c1c; + --tridactyl-cmdl-small-font-size: 12pt; + --tridactyl-cmplt-bg: #1c1c1c; + --tridactyl-cmplt-small-font-size: 12pt; +} + +/* :root.TridactylThemeR body { */ +/* } */ + +:root.TridactylThemeR #command-line-holder { + order: 1; +} + +:root.TridactylThemeR #completions { + order: 2; + border-top: 1px solid var(--tridactyl-complt-bg); +} + +:root.TridactylThemeR #completions .sectionHeader { + display: none; +} + +/* :root.TridactylThemeR #tridactyl-colon { */ +/* margin-left: 2px; */ +/* } */ +:root.TridactylThemeR #tridactyl-colon::before { + content: " > "; +} + +/* :root.TridactylThemeR #completions .focused, */ +/* :root.TridactylThemeR #completions .focused .url { */ +/* background: var(--tridactyl-bg-second); */ +/* text-decoration: italic; */ +/* } */ + +:root.TridactylThemeR #cmdline_iframe { + position: fixed !important; + bottom: unset; + top: -1px !important; + left: 0% !important; + box-shadow: rgba(0, 0, 0, 0.5) 0px 0px 15px !important; + /* opacity: 0.9; */ +} + +/* :root.TridactylThemeR #changelog { */ +/* background: var(--tridactyl-bg-second); */ +/* } */ diff --git a/.config/tridactyl/tridactylrc b/.config/tridactyl/tridactylrc new file mode 100644 index 0000000..5ea41a0 --- /dev/null +++ b/.config/tridactyl/tridactylrc @@ -0,0 +1,105 @@ +" vim: ft=vim +" sanitise tridactyllocal tridactylsync + +set browser firefox-nightly +set allowautofocus false +set searchengine duckduckgo +set tabopencontaineraware true +set editorcmd alacritty -t tridactyledit -e nvim +set historyresults 50 +set newtabfocus page +set allowautofocus false + +colors r + +" remove annoying binds +unbind s +unbind <C-f> +unbind <C-a> +unbind <F1> + +bind <Space>tw fillcmdline tabopen -c wee +bind <Space>tp fillcmdline tabopen -c pers +bind <Space>tb fillcmdline tabopen -c bank +bind <Space>ts fillcmdline tabopen -c shop + +bind <space>s fillcmdline tabopen search +bind <space>n fillcmdline nativeopen + +bind / fillcmdline find +bind n findnext 1 +bind N findnext -1 +bind <Space>, nohlsearch + +bind <Space>l fillcmdline urlmodify -g + +" select all in cmdline +unbind --mode=ex <C-a> + +bindurl news.ycombinator.com ;c hint -fr \d+\scomments +bindurl news.ycombinator.com f hint -c [class*="storylink"] + +" better result hinting on ddg +bindurl duckduckgo.com f hint -c [class*="result__url"] +bindurl duckduckgo.com F hint -b -c [class*="result__url"] + +" hint: mpv/video +bind gv composite get_current_url | !s mpv --profile=cache +bind yv composite js navigator.clipboard.readText() | !s mpv --profile=cache +bind ;v hint -W !s mpv --profile=cache + +" hint: other +bind ;i hint -W !s imv +bind ;e hint -W !s alacritty --class '__floating-tridactyledit' -e nvim + +bind gp fillcmdline winopen -popup +bind g1 tab 2 +bind g2 tab 3 +bind g3 tab 4 +bind g4 tab 5 + +" some autocommands that rewrite URLs +" autocmd DocStart www.amazon.co.uk js tri.excmds.urlmodify("-t", "www", "smile") +" autocmd DocStart *.solus-project.com js tri.excmds.urlmodify("-t", "solus-project.com", "getsol.us") +" autocmd DocStart golang.org/pkg js tri.excmds.urlmodify("-t", "golang.org/pkg", "godoc.org") +" autocmd DocStart typeform.com mode ignore + +" autocontainers +" aucon meetup.com personal +" aucon trello.com personal +" aucon reddit.com weeb +" aucon kitsu.io weeb +" aucon youtube.com weeb +" aucon shanaproject.com weeb +" aucon kum4.cf weeb +" aucon nyaa.si weeb +" aucon amazon.(com|de|co.jp|co.uk) shopping +" aucon flowdock.com work +" aucon calamari.io work + +seturl overleaf.com mode ignore + +" fix youtube +seturl youtube.com noiframe true +unbindurl youtube.com f +bindurl youtube.com h hint +bindurl youtube.com T tabopen ddg.gg + +" fix reddit +seturl reddit.com allowautofocus true +unbindurl reddit.com <Escape> +unbindurl reddit.com j +unbindurl reddit.com k +unbindurl reddit.com n +unbindurl reddit.com p +unbindurl reddit.com a +unbindurl reddit.com z +unbindurl reddit.com c +" unbindurl reddit.com r +unbindurl reddit.com s +unbindurl reddit.com h +unbindurl reddit.com x +unbindurl reddit.com l + +set leavegithubalone false +set blacklistkeys ["/","<Space>",";"] diff --git a/.config/vifm/colors/papercolor_dark.vifm b/.config/vifm/colors/papercolor_dark.vifm new file mode 100644 index 0000000..9f79ae4 --- /dev/null +++ b/.config/vifm/colors/papercolor_dark.vifm @@ -0,0 +1,52 @@ +" Available attributes (some of them can be combined): +" bold +" underline +" reverse or inverse +" standout +" none + +" let s:palette = { +" \ 'color00' : ['#1c1c1c', '234'], +" \ 'color01' : ['#af005f', '125'], +" \ 'color02' : ['#5faf00', '70'], +" \ 'color03' : ['#d7af5f', '179'], +" \ 'color04' : ['#5fafd7', '74'], +" \ 'color05' : ['#808080', '244'], +" \ 'color06' : ['#d7875f', '173'], +" \ 'color07' : ['#d0d0d0', '252'], +" \ 'color08' : ['#585858', '240'], +" \ 'color09' : ['#5faf5f', '71'], +" \ 'color10' : ['#afd700', '148'], +" \ 'color11' : ['#af87d7', '140'], +" \ 'color12' : ['#ffaf00', '214'], +" \ 'color13' : ['#ff5faf', '205'], +" \ 'color14' : ['#00afaf', '37'], +" \ 'color15' : ['#5f8787', '66'], +" \ 'color16' : ['#5fafd7', '74'], +" \ 'color17' : ['#d7af00', '178'], +" \ } + +highlight clear + +highlight Win cterm=none ctermfg=252 ctermbg=234 +highlight Directory cterm=bold ctermfg=74 ctermbg=default +highlight Link cterm=bold ctermfg=173 ctermbg=default +highlight BrokenLink cterm=bold ctermfg=240 ctermbg=default +highlight Socket cterm=bold ctermfg=205 ctermbg=default +highlight Device cterm=bold ctermfg=125 ctermbg=default +highlight Fifo cterm=bold ctermfg=148 ctermbg=default +highlight Executable cterm=bold ctermfg=70 ctermbg=default +highlight Selected cterm=standout ctermfg=140 ctermbg=default +highlight CurrLine cterm=bold,reverse ctermfg=default ctermbg=default + +highlight TopLine cterm=none ctermfg=default ctermbg=default +highlight TopLineSel cterm=bold ctermfg=default ctermbg=default +highlight StatusLine cterm=bold ctermfg=234 ctermbg=74 +highlight WildMenu cterm=underline,reverse ctermfg=white ctermbg=black +highlight CmdLine cterm=none ctermfg=default ctermbg=default +highlight ErrorMsg cterm=none ctermfg=125 ctermbg=default +highlight Border cterm=none ctermfg=default ctermbg=default +highlight JobLine cterm=bold,reverse ctermfg=default ctermbg=default +highlight SuggestBox cterm=bold ctermfg=default ctermbg=default +highlight CmpMismatch cterm=bold ctermfg=252 ctermbg=125 +highlight AuxWin cterm=bold,underline,reverse,standout ctermfg=default ctermbg=default diff --git a/.config/vifm/vifmrc b/.config/vifm/vifmrc new file mode 100644 index 0000000..6af0b3f --- /dev/null +++ b/.config/vifm/vifmrc @@ -0,0 +1,216 @@ +" vim: filetype=vifm : + +colorscheme papercolor_dark + +set vicmd=nvim +set trash +set history=100 +set nofollowlinks +set sortnumbers +set undolevels=100 +set vimhelp +set norunexec +set timefmt=%m/%d\ %H:%M +set wildmenu +set wildstyle=popup +set suggestoptions=normal,visual,view,otherpane,keys,marks,registers +set ignorecase +set smartcase +set nohlsearch +set incsearch +set scrolloff=4 + +" Don't do too many requests to slow file systems +if !has('win') + set slowfs=curlftpfs +endif + +set statusline=" Hint: %z%= %A %10u:%-7g %15s %20d " + +" ------------------------------------------------------------------------------ + +mark b ~/bin/ +mark h ~/ + +" ------------------------------------------------------------------------------ + +" :com[mand][!] command_name action +" The following macros can be used in a command +" %a is replaced with the user arguments. +" %c the current file under the cursor. +" %C the current file under the cursor in the other directory. +" %f the current selected file, or files. +" %F the current selected file, or files in the other directory. +" %b same as %f %F. +" %d the current directory name. +" %D the other window directory name. +" %m run the command in a menu window + +command! df df -h %m 2> /dev/null +command! diff vi -d %f %F +command! zip zip -r %f.zip %f +command! run !! ./%f +command! make !!make %a +command! mkcd :mkdir %a | cd %a +command! vgrep vi "+grep %a" +command! reload :write | restart + +command ssh +\ : let $SSH_FILE = system('mktemp /tmp/vifm-XXXX.ssh') +\ | execute '!echo ''%a'' > $SSH_FILE; ls $SSH_FILE %%U' +\ | execute 'normal l' +\ | execute '!rm $SSH_FILE' + +" ------------------------------------------------------------------------------ + +" Pdf +filextype *.pdf + \ {View in mupdf} + \ mupdf %c &, + \ {View in zathura} + \ zathura %c %i &, +fileviewer *.pdf pdftotext -nopgbrk %c - +" PostScript +filextype *.ps,*.eps,*.ps.gz + \ {View in zathura} + \ zathura %f, +" Djvu +filextype *.djvu + \ {View in zathura} + \ zathura %f, + +" Audio +filetype *.wav,*.mp3,*.flac,*.m4a,*.wma,*.ape,*.ac3,*.og[agx],*.spx,*.opus + \ {Play using mpv} + \ mpv --no-video %c, + \ {Play using ffplay} + \ ffplay -nodisp %c, + \ {Enqueue using xap} + \ xap add %c:p, +fileviewer *.mp3 mp3info + +" Video +filextype *.avi,*.mp4,*.wmv,*.dat,*.3gp,*.ogv,*.mkv,*.mpg,*.mpeg,*.vob, + \*.fl[icv],*.m2v,*.mov,*.webm,*.ts,*.mts,*.m4v,*.r[am],*.qt,*.divx, + \*.as[fx] + \ {View using mpv} + \ mpv %f, + \ {View using ffplay} + \ ffplay -fs %f, +fileviewer *.avi,*.mp4,*.wmv,*.dat,*.3gp,*.ogv,*.mkv,*.mpg,*.mpeg,*.vob, + \*.fl[icv],*.m2v,*.mov,*.webm,*.ts,*.mts,*.m4v,*.r[am],*.qt,*.divx, + \*.as[fx] + \ ffprobe -pretty %c 2>&1 + +" Web +filextype *.html,*.htm + \ {Open with w3m} + \ w3m %f %i &, + \ {Open with firefox} + \ firefox %f &, + +" Object +filetype *.o nm %f | less + +" Man page +filetype *.[1-8] man ./%c +fileviewer *.[1-8] man ./%c | col -b + +" Images +filextype *.bmp,*.jpg,*.jpeg,*.png,*.gif,*.xpm + \ {View in imv} + \ imv -n %c %d 2>/dev/null &, +fileviewer *.bmp,*.jpg,*.jpeg,*.png,*.gif,*.xpm + \ convert -identify %f -verbose /dev/null + +" MD5 +filetype *.md5 + \ {Check MD5 hash sum} + \ md5sum -c %f %S, +" SHA1 +filetype *.sha1 + \ {Check SHA1 hash sum} + \ sha1sum -c %f %S, +" SHA256 +filetype *.sha256 + \ {Check SHA256 hash sum} + \ sha256sum -c %f %S, +" SHA512 +filetype *.sha512 + \ {Check SHA512 hash sum} + \ sha512sum -c %f %S, +" GPG signature +filetype *.asc + \ {Check signature} + \ !!gpg --verify %c, + +filetype *.zip,*.jar,*.war,*.ear,*.oxt,*.apkg + \ {View contents} + \ zip -sf %c | less, + \ {Extract here} + \ tar -xf %c, +filetype *.zip + \ {Extract ZIP} + \ unzip %c +fileviewer *.zip,*.jar,*.war,*.ear,*.oxt zip -sf %c + +" ArchiveMount +fileviewer *.tgz,*.tar.gz tar -tzf %c +fileviewer *.tar.bz2,*.tbz2 tar -tjf %c +fileviewer *.tar.txz,*.txz xz --list %c +fileviewer *.tar tar -tf %c + +" ------------------------------------------------------------------------------ + +" What should be saved automatically between vifm runs +" Like in previous versions of vifm +" set vifminfo=options,filetypes,commands,bookmarks,dhistory,state,cs +" Like in vi +set vifminfo=dhistory,savedirs,chistory,state,tui,shistory, + \phistory,fhistory,dirstack,registers,bookmarks,bmarks + +" ------------------------------------------------------------------------------ + +" Examples of configuring both panels + +" Customize view columns a bit (enable ellipsis for truncated file names) +" +" set viewcolumns=-{name}..,6{}. + +" Filter-out build and temporary files +" +" filter! /^.*\.(lo|o|d|class|py[co])$|.*~$/ + +" ------------------------------------------------------------------------------ + +" Start shell in current directory +nnoremap s :shell<cr> +" Display sorting dialog +nnoremap S :sort<cr> + +" Toggle visibility of preview window +nnoremap w :view<cr> +vnoremap w :view<cr>gv + +" Open file in the background using its default program +nnoremap gb :file &<cr>l + +" Yanking... +nnoremap yf :!echo -n %c:p | wl-copy %i<cr> +nnoremap yn :!echo -n %c | wl-copy %i<cr> +nnoremap yd :!echo -n %d | wl-copy %i<cr> +nnoremap yff :!cat -n %c:p | wl-copy %i<cr> + +" Mappings for faster renaming +nnoremap I cw<c-a> +nnoremap cc cw<c-u> +nnoremap A cw + +" Open console in current directory +nnoremap ,t :!alacritty &<cr> +" Open editor to edit vifmrc and apply settings after returning to vifm +nnoremap ,c :write | edit $MYVIFMRC | restart<cr> +" Toggle wrap setting on ,w key +nnoremap ,w :set wrap!<cr> + +nnoremap q :quit<cr> diff --git a/.config/waybar/config b/.config/waybar/config new file mode 100644 index 0000000..6f29e7e --- /dev/null +++ b/.config/waybar/config @@ -0,0 +1,168 @@ +// vim: commentstring=//\ %s +{ + "layer": "top", + "position": "top", + "height": 30, + + "modules-left": ["sway/mode", "sway/workspaces", "sway/window", "custom/media", "custom/toggl"], + // "modules-center": ["sway/window"], + "modules-right": ["cpu", "memory", "temperature", "idle_inhibitor", "backlight", "pulseaudio", "network", "battery", "clock#local", "clock#utc", "tray"], + "sway/workspaces": { + "disable-scroll": true, + "all-outputs": false, + "format": "{icon}", + "format-icons": { + // "urgent": "", + // "focused": "", + // "default": "", + // "1": "", + // "2": "", + // "3": "", + // "4": "", + // "5": "", + // "6": "", + // "7": "", + // "8": "", + // "9": "", + "m": "", + "v": "", + "g": "" + } + }, + "sway/window": { + "format": "{}", + "max-length": 50, + "tooltip": false + }, + "sway/mode": { + "format": "{}", + "tooltip": false + }, + "idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + } + }, + "tray": { + "spacing": 10 + }, + "clock#local": { + "tooltip": false, + "format": "{:<i>%a</i> %d %b %H:%M}", + "timezone": "Europe/Berlin" + }, + "clock#utc": { + "tooltip": false, + "format": "(UTC {:%H:%M})", + "timezone": "UTC" + }, + "cpu": { + "format": "{usage}% ", + "tooltip": true + }, + "memory": { + "format": "{}% " + }, + "temperature": { + "critical-threshold": 90, + "format": "{temperatureC}℃ {icon}", + "format-icons": ["", "", ""] + }, + "backlight": { + "format": "{percent}% {icon}", + "format-icons": [""] + }, + "battery": { + "states": { + // "good": 95, + "warning": 30, + "critical": 15 + }, + "format": "{capacity}% {icon}", + "format-charging": "{capacity}% ", + "format-plugged": "{capacity}% ", + "format-alt": "{time} {icon}", + "format-full": "{icon}", + "format-icons": ["", "", "", "", ""], + "tooltip": false + }, + "network": { + "format-wifi": "{essid} ({signalStrength}%) ", + "format-ethernet": "{ifname}: {ipaddr}/{cidr} ", + "format-linked": "{ifname} (No IP) ", + "format-disconnected": "Disconnected ", + "format-alt": "{ifname}: {ipaddr}/{cidr}", + "tooltip": false + }, + "network#vpn": { + "interface": "tun0", + "format-wifi": " {ifname}", + "format-ethernet": " {ifname}", + "format-disconnected": "", + "tooltip": false + }, + "pulseaudio": { + "format": "{volume}% {icon}", + "format-bluetooth": "{volume}% {icon}", + "format-muted": "{volume}% ", + "format-source": "{volume}% ", + "format-source-muted": "{volume}% ", + "format-icons": { + "headphones": "", + "handsfree": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "tooltip": false, + "on-click-right": "pactl set-sink-mute @DEFAULT_SINK@ toggle", + "on-click": "pavucontrol" + }, + "custom/media": { + "format": "{icon} {}", + "return-type": "json", + "max-length": 80, + "format-icons": { + "spotify": "", + "mpv": "", + "default": "" + }, + "on-click": "sway [workspace=$wsMusic] focus", + "escape": false, + "exec": "$HOME/.config/waybar/mediaplayer.py -I chromium 2> /dev/null" + }, + "custom/toggl": { + "exec": "$HOME/.config/waybar/toggl.lua 2>/dev/null", + "interval": 30 + }, + "mpd": { + "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ", + "format-disconnected": "Disconnected", + "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped", + "unknown-tag": "N/A", + "interval": 2, + "consume-icons": { + "on": " " + }, + "random-icons": { + "off": "", + "on": " " + }, + "repeat-icons": { + "on": " " + }, + "single-icons": { + "on": "1 " + }, + "state-icons": { + "paused": "", + "playing": "" + }, + "tooltip-format": "MPD (connected)", + "tooltip-format-disconnected": "MPD (disconnected)" + } +} diff --git a/.config/waybar/mediaplayer.py b/.config/waybar/mediaplayer.py new file mode 100755 index 0000000..ce2314d --- /dev/null +++ b/.config/waybar/mediaplayer.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +import argparse +import json +import logging +import signal +import sys +from datetime import timedelta +from math import floor + +# yapf: disable +import gi +gi.require_version('Playerctl', '2.0') +from gi.repository import GLib, Playerctl +# yapf: enable + +logger = logging.getLogger(__name__) + + +def write_output(text, player): + logger.info('Writing output') + + output = { + 'text': text, + 'class': 'custom-' + player.props.player_name, + 'alt': player.props.player_name + } + + sys.stdout.write(json.dumps(output) + '\n') + sys.stdout.flush() + + +def on_play(player, status, manager): + logger.info('Received new playback status') + on_metadata(player, player.props.metadata, manager) + + +def on_metadata(player, metadata, manager): + logger.info('Received new metadata') + track_info = '' + + if player.props.player_name == 'spotify' and \ + 'mpris:trackid' in metadata.keys() and \ + ':ad:' in player.props.metadata['mpris:trackid']: + track_info = 'AD PLAYING' + + if player.get_title() and player.get_title() != '': + track_info = f"{player.get_title()}" + if player.get_artist() and player.get_artist() != '': + track_info = f"{player.get_artist()} - {track_info}" + + if 'mpris:length' in player.props.metadata.keys(): + dur_td = timedelta(microseconds=player.props.metadata['mpris:length']) + dur_h, remainder = divmod(dur_td.total_seconds(), 3600) + dur_m, dur_s = divmod(remainder, 60) + dur = f"{floor(dur_m)}:{floor(dur_s):02}" + if dur_h > 0: + dur = f"{floor(dur_h)}:{dur}" + track_info = '{} ({})'.format(track_info, str(dur)) + + if player.props.status == 'Paused' and track_info: + track_info = ' ' + track_info + + if player.props.status == 'Stopped': + return + + if track_info == '': + logger.warning('TrackInfo was empty after parsing') + return + + write_output(track_info, player) + + +def on_player_appeared(manager, player, selected_player=None): + if player is not None and (selected_player is None + or player.name == selected_player): + init_player(manager, player) + else: + logger.debug( + "New player appeared, but it's not the selected player, skipping") + + +def on_player_vanished(manager, player): + logger.info('Player has vanished') + sys.stdout.write('\n') + sys.stdout.flush() + + +def init_player(manager, name): + logger.debug('Initialize player: {player}'.format(player=name.name)) + player = Playerctl.Player.new_from_name(name) + player.connect('playback-status', on_play, manager) + player.connect('metadata', on_metadata, manager) + manager.manage_player(player) + on_metadata(player, player.props.metadata, manager) + + +def signal_handler(sig, frame): + logger.debug('Received signal to stop, exiting') + sys.stdout.write('\n') + sys.stdout.flush() + # loop.quit() + sys.exit(0) + + +def parse_arguments(): + parser = argparse.ArgumentParser() + + # Increase verbosity with every occurance of -v + parser.add_argument('-v', '--verbose', action='count', default=0) + + # Define for which player we're listening + parser.add_argument('--player', + help='player to listen for') + + # Define which players to ignore entirely + parser.add_argument('-I', '--ignore', + action='append', + help='players to ignore') + + return parser.parse_args() + + +def main(): + arguments = parse_arguments() + + # Initialize logging + logging.basicConfig(stream=sys.stderr, + level=logging.DEBUG, + format='%(name)s %(levelname)s %(message)s') + + # Logging is set by default to WARN and higher. + # With every occurrence of -v it's lowered by one + logger.setLevel(max((3 - arguments.verbose) * 10, 0)) + + # Log the sent command line arguments + logger.debug('Arguments received {}'.format(vars(arguments))) + + manager = Playerctl.PlayerManager() + loop = GLib.MainLoop() + + manager.connect('name-appeared', + lambda *args: on_player_appeared(*args, arguments.player)) + manager.connect('player-vanished', on_player_vanished) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + for player in manager.props.player_names: + if arguments.player is not None and arguments.player != player.name: + logger.debug( + '{player} is not the filtered player, skipping it'.format( + player=player.name)) + continue + + if arguments.ignore is not None and player.name in arguments.ignore: + logger.debug( + '{player} is ignored, skipping it'.format( + player=player.name)) + continue + + init_player(manager, player) + + loop.run() + + +if __name__ == '__main__': + main() diff --git a/.config/waybar/style.css b/.config/waybar/style.css new file mode 100644 index 0000000..f832fd6 --- /dev/null +++ b/.config/waybar/style.css @@ -0,0 +1,194 @@ +@define-color foreground #fffefe; +@define-color background #221e1d; +@define-color accent #005CAF; +@define-color accentdarker #0B346F; +@define-color dimmed #777777; +@define-color dimmeddarker #302a29; +@define-color urgent #db4d6d; + +* { + border: none; + border-radius: 0; + font-family: Iosevka Medium, Noto Sans, Font Awesome, monospace; + font-size: 12pt; + min-height: 0; +} + +window#waybar { + background: @background; + /* opacity: 0.98; */ + /* border-bottom: 3px solid rgba(100, 114, 125, 0.5); */ + color: @foreground; + transition-property: background-color; + transition-duration: 0.1s; +} + +window#waybar.hidden { + opacity: 0.2; +} +/* window#waybar.empty {} */ +/* window#waybar.solo {} */ +/* window#waybar.<appid> {} */ + +#workspaces button { + padding: 0 5px; + /* background-color: transparent; */ + background: transparent; + color: @dimmed; + /* border-bottom: 3px solid transparent; */ +} + +/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ +#workspaces button:hover { + background: @dimmeddarker; + box-shadow: inherit; + text-shadow: inherit; +} + +#workspaces button.focused { + color: @foreground; + background: @accentdarker; +} +#workspaces button.focused:hover { + background: @accent; +} + +#workspaces button.urgent { + color: @background; + background-color: @urgent; +} + +#window { + color: @foreground; + border-top: 2px solid @accent; +} + +#mode { + font-style: italic; + background-color: #64727d; + border-top: 2px solid @foreground; +} + +#clock, +#battery, +#cpu, +#memory, +#temperature, +#backlight, +#network, +#pulseaudio, +#custom-media, +#custom-toggl, +#tray, +#mode, +#window, +#idle_inhibitor { + padding: 0 0.3rem; + margin: 0 0.25rem; + color: @foreground; + border-top: 2px solid @dimmeddarker; +} + +#clock { + /* background-color: #64727d; */ +} +#clock.local { + margin-right: 0; + padding-right: 0; +} +#clock.utc { + margin: 0; + padding: 0; + padding-left: 0.3rem; + color: @dimmed; +} + +#battery { +} +#battery.charging { + color: @foreground; + background-color: #26a65b; +} + +@keyframes blink { + to { + background-color: @foreground; + color: @background; + } +} + +#battery.critical:not(.charging) { + background-color: #f53c3c; + color: @foreground; + animation-name: blink; + animation-duration: 0.5s; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-direction: alternate; +} + +label:focus { + background-color: @background; +} + +#cpu { +} +#memory { +} +#backlight { +} +#network { +} +#network.disconnected { + color: @dimmed; +} +#pulseaudio { +} +#pulseaudio.muted { + color: @dimmed; +} + +#custom-media { + min-width: 100px; + color: @foreground; + border-top: 2px solid #628aaa; +} +#custom-media.custom-spotify, +#custom-media.custom-spotifyd { + border-top: 2px solid #1DB954; +} +#custom-media.custom-mpv { + border-top: 2px solid #520053; +} + +#custom-toggl { + color: @foreground; + /* background-color: #DB4D6D; */ + border-top: 2px solid #DB4D60; +} + +#temperature { +} + +#temperature.critical { + background-color: #eb4d4b; +} + +#tray { +} + +#idle_inhibitor { + color: @dimmed; +} +#idle_inhibitor.activated { + color: unset; +} + +#mpd { +} +#mpd.disconnected { +} +#mpd.stopped { +} +#mpd.paused { +} diff --git a/.config/waybar/toggl.lua b/.config/waybar/toggl.lua new file mode 100755 index 0000000..e44fce2 --- /dev/null +++ b/.config/waybar/toggl.lua @@ -0,0 +1,28 @@ +#!/usr/bin/env luajit + +TOKEN = os.getenv("TOGGL_TOKEN") +if not TOKEN then + io.stderr:write("need TOGGL_TOKEN\n") + os.exit(1) +end + +function timer_duration() + cmd = string.format('toggl -t %s -j timer ls | jq -r ".[0].duration"', TOKEN) + local f = assert(io.popen(cmd, 'r')) + local s = assert(f:read('*a')) + f:close() + if not (s == '') then + return tonumber(s) + end +end + +unix_time = os.time() + +diff = timer_duration() +if not diff then + os.exit(0) +end + +d = unix_time + diff + +print(string.format(' %dh %.2dm', d/(60*60), d/60%60)) diff --git a/.config/wofi/config b/.config/wofi/config new file mode 100644 index 0000000..35e18a7 --- /dev/null +++ b/.config/wofi/config @@ -0,0 +1,2 @@ +allow_images=true +# allow_markup=true diff --git a/.config/wofi/style.css b/.config/wofi/style.css new file mode 100644 index 0000000..38f811b --- /dev/null +++ b/.config/wofi/style.css @@ -0,0 +1,16 @@ +window { + font-family: Iosevka; + font-size: large; +} + +#input { + margin: .5em; +} + +#text { + margin: .2em .5em; +} + +#img { + -gtk-icon-transform: scale(.6); +} diff --git a/.config/yamlfs/balena.yml b/.config/yamlfs/balena.yml new file mode 100644 index 0000000..5fccdd2 --- /dev/null +++ b/.config/yamlfs/balena.yml @@ -0,0 +1,18 @@ +docker-compose.yml: |- + version: '2.1' + + services: + build: ./container + + volumes: + ctr-data: + +container: + Dockerfile.template: |- + # vim: ft=dockerfile + FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine:run + + # RUN apk add --no-cache + + # COPY ./start.sh /usr/local/bin/ + # CMD [ "start.sh" ] diff --git a/.config/yamlfs/go.yml b/.config/yamlfs/go.yml new file mode 100644 index 0000000..f7797be --- /dev/null +++ b/.config/yamlfs/go.yml @@ -0,0 +1,21 @@ +cmd: + app: + main.go: |- + package main + + func main() { + println("hello world") + } + +go.mod: |- + module example.com/app + + go 1.13 + +contrib: + data: {} + +README.md: |- + # app + + this is an app diff --git a/.config/zathura/zathurarc b/.config/zathura/zathurarc new file mode 100644 index 0000000..80811d4 --- /dev/null +++ b/.config/zathura/zathurarc @@ -0,0 +1,33 @@ +set window-title-home-tilde true +set statusbar-basename true +set selection-clipboard clipboard +set recolor "true" + +set completion-bg "#1c1c1c" +set completion-fg "#d0d0d0" + +set completion-highlight-bg "#5faf00" +set completion-highlight-fg "#1c1c1c" + +set inputbar-bg "#1c1c1c" +set inputbar-fg "#d0d0d0" + +set statusbar-bg "#1c1c1c" +set statusbar-fg "#d0d0d0" + +set recolor-lightcolor "#1c1c1c" +set recolor-darkcolor "#d0d0d0" +set default-bg "#1c1c1c" + +set index-bg "#585858" +set index-fg "#d0d0d0" +set index-active-bg "#1c1c1c" +set index-active-fg "#d0d0d0" + +map P print +map c recolor +map R rotate rotate-ccw +map n zoom in +map p zoom out +map M mark_add +map m mark_evaluate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31b33ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +* + +# To track a file using the dotfile repo add it using: git add -f FILE +# or exclude below + diff --git a/.irssi/pipeline.theme b/.irssi/pipeline.theme new file mode 100644 index 0000000..15b5bff --- /dev/null +++ b/.irssi/pipeline.theme @@ -0,0 +1,270 @@ +#!/bin/bash +# +# pipeline +# +# An Irssi theme inspired by madcow and skeita +# +# Copyright (c) 2016 Beau Hastings. All rights reserved. +# License: GNU General Public License v2 +# +# Author: Beau Hastings <beausy@gmail.com> +# URL: https://github.com/hastinbe/irssi-theme-pipeline + +replaces = { "/[]=" = "%K$*%n"; "*@+." = "%B$*%n"; }; + +#replaces = { "!@+" = "%r$*%n"; }; +info_eol = "false"; + +abstracts = { + ## + ## generic + ## + + # text to insert at the beginning of each non-message line + #line_start = "%b.%c.%C.%n "; + line_start = ""; + + # timestamp styling, nothing by default + timestamp = "%B$*%n"; + #timestamp = "%y$*%n"; + + # any kind of text that needs hilighting, default is to bold + hilight = "%_$*%_"; + + # any kind of error message, default is bright red + error = "%R$*%n"; + + # channel name is printed + #channel = "$*"; + channel = "%_$*%_"; + + # nick is printed + nick = "$*"; + + # nick host is printed + nickhost = "$*"; + + # server name is printed + server = "%_$*%_"; + + # some kind of comment is printed + #comment = "$*"; + comment = "%b[%n$*%b]%n"; + + # reason for something is printed (part, quit, kick, ..) + reason = "{comment %_$*%_}"; + + # mode change is printed ([+o nick]) + #mode = "{comment %n[%W$*]%n}"; + mode = "%b$*%n"; + + ## + ## channel specific messages + ## + + # highlighted nick/host is printed (joins) + channick_hilight = "$*"; + chanhost_hilight = "{nickhost $*}"; + + # nick/host is printed (parts, quits, etc.) + channick = "%W$*%n"; + chanhost = "{nickhost $*}"; + + # highlighted channel name is printed + channelhilight = "%R$*%n"; + + # ban/ban exception/invite list mask is printed + ban = "%r$*%n"; + + ## + ## messages + ## + + # the basic styling of how to print message, $0 = nick mode, $1 = nick + #msgnick = "<%C$0%n$1-%n> %|"; + msgnick = "%b%n$0$1%K |%n %|"; + + # message from you is printed. "msgownnick" specifies the styling of the + # nick ($0 part in msgnick) and "ownmsgnick" specifies the styling of the + # whole line. + + # Example1: You want the message text to be green: + # ownmsgnick = "{msgnick $0 $1-}%g"; + # Example2.1: You want < and > chars to be yellow: + # ownmsgnick = "%Y{msgnick $0 $1-%Y}%n"; + # (you'll also have to remove <> from replaces list above) + # Example2.2: But you still want to keep <> grey for other messages: + # pubmsgnick = "%K{msgnick $0 $1-%K}%n"; + # pubmsgmenick = "%K{msgnick $0 $1-%K}%n"; + # pubmsghinick = "%K{msgnick $1 $0$2-%n%K}%n"; + # ownprivmsgnick = "%K{msgnick $*%K}%n"; + # privmsgnick = "%K{msgnick %R$*%K}%n"; + + # $0 = nick mode, $1 = nick + ownmsgnick = "{msgnick $0 $1-}%W"; + ownnick = "%y$*%n"; + + # public message in channel, $0 = nick mode, $1 = nick + pubmsgnick = "{msgnick %b$0%n %w$1%n}"; + #pubmsgnick = "{msgnick $0$1-}"; + pubnick = "%w$*%n"; + + # public message in channel meant for me, $0 = nick mode, $1 = nick + #pubmsgmenick = "%Y{msgnick %B$0%n %w$1%n}%w"; + pubmsgmenick = "{msgnick $0$1-}"; + menick = "%g$*%n"; + + # public highlighted message in channel + # $0 = highlight color, $1 = nick mode, $2 = nick + #pubmsghinick = "%Y{msgnick %B$1 %w$2}%w"; + pubmsghinick = "{msgnick $1 $0$2-%n}"; + + # channel name is printed with message + msgchannel = "%b:$*%n"; + + # private message, $0 = nick, $1 = host + #privmsg = "%b.%c.%C.%n(%c$0%n[%c$1]) "; + privmsg = "[%w$0!%b$1-%n] "; + + # private message from you, $0 = "msg", $1 = target nick + #ownprivmsg = "%b.%c.%C.%n[%cmsg%n(%c$1%n)] "; + ownprivmsg = "{msgnick $*}"; + + # own private message in query + ownprivmsgnick = "{msgnick %W$*%w}"; + ownprivnick = "%W$0%n%w"; + + # private message in query + privmsgnick = "{msgnick %B$*%w}"; + + ## + ## Actions (/ME stuff) + ## + + # used internally by this theme + action_core = "%W*%w $*%n"; + + # generic one that's used by most actions + action = "%w{action_core %w$*} "; + + # own action, both private/public + ownaction = "{action $*}"; + + # own action with target, both private/public + ownaction_target = "{action_core $0}%K:%b$1%n "; + + # private action sent by others + pvtaction = "%w $*%n "; + pvtaction_query = "{action $*}"; + + # public action sent by others + pubaction = "{action $*}"; + + + ## + ## other IRC events + ## + + # notices + ownnotice = "%b>%c>%C> %c$0%B!%c$1%n: "; + notice = "%b$*%n "; + pubnotice_channel = "%K:%m$*"; + pubnotice_host = "%K(%m$*%K)"; + servernotice = "%g!$*%n "; + + # CTCPs + ownctcp = "%b>%c>%C> %c$0%B!%c$1%n: "; + ctcp = "%m$*%n"; + + # wallops + wallop = "%W$*%n: "; + wallop_nick = "%n$*"; + wallop_action = "%W * $*%n "; + + # netsplits + netsplit = "%B$*%n"; + netjoin = "%B$*%n"; + + # /names list + names_nick = "[%_$0%_%w$1-%n] "; + names_users = "%b$*%n:"; + names_channel = "%B$*%n"; + + # DCC + dcc = "%b$*%n"; + dccfile = "%_$*%_"; + + # DCC chat, own msg/action + dccownmsg = "[%b$0%K($1-%K)%n] "; + dccownnick = "%B$*%n"; + dccownaction = "{action $*}"; + dccownaction_target = "{action_core $0}%K:%b$1%n "; + + # DCC chat, others + dccmsg = "[%B$1-%K(%b$0%K)%n] "; + dccquerynick = "%B$*%n"; + dccaction = "%W (*dcc*) $*%n %|"; + + ## + ## statusbar + ## + + # background of statusbar + sb_background = "%0"; + + prompt = "%w$*%n%B:%W "; + + # default statusbar item style + sb = "%B[%W$*%B]%n"; + sbmode = " %b+%n$*"; + sbaway = " (%Baway%n)"; + sbservertag = ":%W$0"; + sbmore = "%_-- more --%_"; + sblag = "{sb L: %B$*}"; + sbmail = "{sb M: $*}"; + + # activity. Det is used for hilights when display doesn't support colors + sbact = "{sb {sbact_act $0}{sbact_det $1}}"; + sbact_act = "A: $*"; + sbact_det = " D: $*"; + + # usercount + sb_usercount = "{sb %W$0%n $1-}"; + sb_uc_ircops = "%_*%_%K/%R$*%n"; + sb_uc_ops = "%_@%_%K/%G$*%n"; + sb_uc_halfops = "%_=%_%K/%g$*%n"; + sb_uc_voices = "%_+%_%K/%Y$*%n"; + sb_uc_normal = "%_.%_%K/%W$*%n"; + sb_uc_space = " "; + +}; + +# %r%n%_$0%_$1%K |%n %| +formats = { + "fe-common/core" = { + pubmsg = "{pubmsgnick $2 {pubnick $[-16]0}}$1"; + join = " %_%Bjoin%_%K |%n %g{channick_hilight $0}%K!%n{chanhost_hilight $1}"; + part = " %_%Bpart%_%K |%n %W{channick $0}%K!%n{chanhost $1}: {reason $3}"; + kick = " %_%Bkick%_%K |%n %r{channick $0} %nby {nick $2} from {channel $1}: {reason $3}"; + quit = " %_%Bquit%_%K |%n %W{channick $0}%K!%n{chanhost $1}: {reason $2}"; + nick_changed = " %_%Bnick%_%K |%n {nick %w$0%n} %Nis now {nick %W$1%n}"; + endofnames = "{channel $0}: {hilight $1} nicks ({comment @/{hilight $2} +/{hilight $3} -/{hilight $4}})"; + own_msg = "{ownmsgnick $2 {ownnick $[-16]0}}$1"; + own_msg_channel = "{ownmsgnick $3 {ownnick $[-16]0}{msgchannel $1}}$2"; + pubmsg_me = "{pubmsgmenick $2 {menick $[-16]0}}$1"; + pubmsg_me_channel = "{pubmsgmenick $3 {menick $[-16]0}{msgchannel $1}}$2"; + pubmsg_hilight = "{pubmsghinick $0 $3 $[-16]1}$2"; + pubmsg_channel = "{pubmsgnick $3 {pubnick $[-16]0}{msgchannel $1}}$2"; + chanmode_change = " %_%Bmode%_%K |%n {$channel $0} %W{channick_hilight $2} %nsets mode %B{$mode $1}"; + channel_mode = " %_%Bmode%_%K |%n {$channel $0} %W{channick_hilight $2} %nsets mode %B{$mode $1}"; + }; + "fe-common/text" = { window_info_sticky = "%# Sticky : $0"; }; + "fe-common/irc" = { + chanmode_change = " %_%Bmode%_%K |%n %y{channick_hilight $2} %nsets mode %b{$mode $1} %non {$channelhilight $0}"; + whois = "{hilight $0} [{nickhost $1@$2}] [$whois_country]%: ircname : $3"; + server_chanmode_change = "{netsplit ServerMode}/{channelhilight $0}: {mode $1} by {nick $2}"; + whois_server = " server : $1 ({comment $2})"; + own_action = "{ownaction $0}%|$1"; + action_public = "{pubaction $0}%|$1"; + }; +}; diff --git a/.irssi/scripts/adv_windowlist.pl b/.irssi/scripts/adv_windowlist.pl new file mode 100644 index 0000000..e652ae7 --- /dev/null +++ b/.irssi/scripts/adv_windowlist.pl @@ -0,0 +1,2879 @@ +use strict; +use warnings; + +our $VERSION = '1.8'; # 63feb35d8b0a8b6 +our %IRSSI = ( + authors => 'Nei', + contact => 'Nei @ anti@conference.jabber.teamidiot.de', + url => "http://anti.teamidiot.de/", + name => 'adv_windowlist', + description => 'Adds a permanent advanced window list on the right or in a status bar.', + sbitems => 'awl_shared', + license => 'GNU GPLv2 or later', + ); + +# UPGRADE NOTE +# ============ +# for users of 0.7 or earlier series, please note that appearance +# settings have moved to /format, i.e. inside your theme! +# the fifo (screen) has been replaced by an external viewer script + +# Usage +# ===== +# copy the script to ~/.irssi/scripts/ +# +# In irssi: +# +# /run adv_windowlist +# +# In your shell (for example a tmux split): +# +# perl ~/.irssi/scripts/adv_windowlist.pl +# +# To use sbar mode instead: +# +# /toggle awl_viewer +# +# Hint: to get rid of the old [Act:] display +# /statusbar window remove act +# +# to get it back: +# /statusbar window add -after lag -priority 10 act + +# Options +# ======= +# formats can be cleared with /format -delete +# +# /format awl_display_(no)key(_active|_visible) <string> +# * string : Format String for one window. The following $'s are expanded: +# $C : Name +# $N : Number of the Window +# $Q : meta-Keymap +# $H : Start hilighting +# $S : Stop hilighting +# /+++++++++++++++++++++++++++++++++, +# | **** I M P O R T A N T : **** | +# | | +# | don't forget to use $S if you | +# | used $H before! | +# | | +# '+++++++++++++++++++++++++++++++++/ +# key : a key binding that goes to this window could be detected in /bind +# nokey : no such key binding was detected +# active : window would receive the input you are currently typing +# visible : window is also visible on screen but not active (a split window) +# +# /format awl_name_display <string> +# * string : Format String for window names +# $0 : name as formatted by the settings +# +# /format awl_display_header <string> +# * string : Format String for this header line. The following $'s are expanded: +# $C : network tag +# +# /format awl_separator(2) <string> +# * string : Character to use between the channel entries +# variant 2 can be used for alternating separators (only in status bar +# without block display) +# +# /format awl_abbrev_chars <string> +# * string : Character to use when shortening long names. The second character +# will be used if two blocks need to be filled. +# +# /format awl_title <string> +# * string : Text to display in the title string or title bar +# +# /format awl_viewer_item_bg <string> +# * string : Format String specifying the viewer's item background colour +# +# /set awl_prefer_name <ON|OFF> +# * this setting decides whether awl will use the active_name (OFF) or the +# window name as the name/caption in awl_display_*. +# That way you can rename windows using /window name myownname. +# +# /set awl_hide_empty <num> +# * if visible windows without items should be hidden from the window list +# set it to 0 to show all windows +# 1 to hide visible windows without items (negative exempt +# active window) +# +# /set awl_detach <list> +# * list of windows that should be hidden from the window list. you +# can also use /awl detach and /awl attach to manage this +# setting. an optional data_level can be specified with ",num" +# +# /set awl_detach_data <num> +# * num : hide the detached window if its data_level is below num +# +# /set awl_detach_aht <ON|OFF> +# * if enabled, also detach all windows listed in the +# activity_hide_targets setting +# +# /set awl_hide_data <num> +# * num : hide the window if its data_level is below num +# set it to 0 to basically disable this feature, +# 1 if you don't want windows without activity to be shown +# 2 to show only those windows with channel text or hilight +# 3 to show only windows with hilight (negative exempt active window) +# +# /set awl_hide_name_data <num> +# * num : hide the name of the window if its data_level is below num +# (only works in status bar without block display) +# you will want to change your formats to add $H...$S around $Q or $N +# if you plan to use this +# +# /set awl_maxlines <num> +# * num : number of lines to use for the window list (0 to disable, negative +# lock) +# +# /set awl_maxcolumns <num> +# * num : number of columns to use for the window list when using the +# tmux integration (0 to disable) +# +# /set awl_block <num> +# * num : width of a column in viewer mode (negative values = block +# display in status bar mode) +# /+++++++++++++++++++++++++++++++++, +# | ****** W A R N I N G ! ****** | +# | | +# | If your block display looks | +# | DISTORTED, you need to add the | +# | following line to your .theme | +# | file under | +# | abstracts = { : | +# | | +# | sb_act_none = "%K$*"; | +# | | +# '+++++++++++++++++++++++++++++++++/ +# +# /set awl_sbar_maxlength <ON|OFF> +# * if you enable the maxlength setting, the block width will be used as a +# maximum length for the non-block status bar mode too. +# +# /set awl_height_adjust <num> +# * num : how many lines to leave empty in viewer mode +# +# /set awl_sort <-data_level|-last_line|refnum> +# * you can change the window sort order with this variable +# -data_level : sort windows with hilight first +# -last_line : sort windows in order of activity +# refnum : sort windows by window number +# active/server/tag : sort by server name +# lru : sort windows with the last recently used last +# "-" reverses the sort order +# typechecks are supported via ::, e.g. active::Query or active::Irc::Query +# undefinedness can be checked with ~, e.g. ~active +# string comparison can be done with =, e.g. name=(status) +# to make sort case insensitive, use #i, e.g. name#i +# any key in the window hash can be tested, e.g. active/chat_type=XMPP +# multiple criteria can be separated with , or +, e.g. -data_level+-last_line +# +# /set awl_placement <top|bottom> +# /set awl_position <num> +# * these settings correspond to /statusbar because awl will create +# status bars for you +# (see /help statusbar to learn more) +# +# /set awl_all_disable <ON|OFF> +# * if you set awl_all_disable to ON, awl will also remove the +# last status bar it created if it is empty. +# As you might guess, this only makes sense with awl_hide_data > 0 ;) +# +# /set awl_viewer <ON|OFF> +# * enable the external viewer script +# +# /set awl_viewer_launch <ON|OFF> +# * try to auto-launch the viewer under tmux or with a shell command +# /awl restart is required all auto-launch related settings to take +# effect +# +# /set awl_viewer_tmux_position <left|top|right|bottom|custom> +# * try to split in this direction when using tmux for the viewer +# custom : use custom_command setting +# +# /set awl_viewer_xwin_command <shell command> +# * custom command to run in order to start the viewer when irssi is +# running under X +# %A - gets replaced by the command to run the viewer +# %qA - additionally quote the command +# +# /set awl_viewer_custom_command <shell command> +# * custom command to run in order to start the viewer +# +# /set awl_viewer_launch_env <string> +# * specific environment settings for use on viewer auto-launch, +# without the AWL_ prefix +# +# /set awl_shared_sbar <left<right|OFF> +# * share a status bar for the first awl item, you will need to manually +# /statusbar window add -after lag -priority 10 awl_shared +# left : space in cells occupied on the left of status bar +# right : space occupied on the right +# Note: you need to replace "left" AND "right" with the appropriate numbers! +# +# /set awl_path <path> +# * path to the file which the viewer script reads +# +# /set fancy_abbrev <no|head|strict|fancy> +# * how to shorten too long names +# no : shorten in the middle +# head : always cut off the ends +# strict : shorten repeating substrings +# fancy : combination of no+strict +# +# /set awl_custom_xform <perl code> +# * specify a custom routine to transform window names +# example: s/^#// remove the #-mark of IRC channels +# the special flags $CHANNEL / $TAG / $QUERY / $NAME can be +# tested in conditionals +# +# /set awl_last_line_shade <timeout> +# * set timeout to shade activity base colours, to enable +# you also need to add +-last_line to awl_sort +# (requires 256 colour support) +# +# /set awl_no_mode_hint <ON|OFF> +# * whether to show the hint of running the viewer script in the +# status bar +# +# /set awl_mouse <ON|OFF> +# * enable the terminal mouse in irssi +# (use the awl-patched mouse.pl for gestures and commands if you need +# them and disable mouse_escape) +# +# /set awl_mouse_offset <num> +# * specifies where on the screen is the awl status bar +# (0 = on top/bottom, 1 = one additional line in between, +# e.g. prompt) +# you MUST set this correctly otherwise the mouse coordinates will +# be off +# +# /set mouse_scroll <num> +# * how many lines the mouse wheel scrolls +# +# /set mouse_escape <num> +# * seconds to disable the mouse, when not clicked on the windowlist +# + +# Commands +# ======== +# /awl detach <num> +# * hide the current window from the window list. num specifies the +# data_level (optional) +# +# /awl attach +# * unhide the current window from the window list +# +# /awl ack +# * change to the next window with activity, ignoring detached windows +# +# /awl redraw +# * redraws the windowlist. There may be occasions where the +# windowlist can get destroyed so you can use this command to +# force a redraw. +# +# /awl restart +# * restart the connection to the viewer script. + +# Viewer script +# ============= +# When run from the command line, adv_windowlist acts as the viewer +# script to be used together with the irssi script to display the +# window list in a sidebar/terminal of its own. +# +# One optional parameter is accepted, the awl_path +# +# The viewer can be configured by three environment variables: +# +# AWL_HI9=1 +# * interpret %9 as high-intensity toggle instead of bold. This had +# been the default prior to version 0.9b8 +# +# AWL_AUTOFOCUS=0 +# * disable auto-focus behaviour when activating a window +# +# AWL_NOTITLE=1 +# * disable the title bar + +# Nei =^.^= ( anti@conference.jabber.teamidiot.de ) + +no warnings 'redefine'; +use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK}; +use constant SCRIPT_FILE => __FILE__; +no if !IN_IRSSI, strict => (qw(subs refs)); +use if IN_IRSSI, Irssi => (); +use if IN_IRSSI, 'Irssi::TextUI' => (); +use v5.10; +use Encode; +use Storable (); +use IO::Socket::UNIX; +use List::Util qw(min max reduce); +use Hash::Util qw(lock_keys); +use Text::ParseWords qw(shellwords); + +BEGIN { + if ($] < 5.012) { + *CORE::GLOBAL::length = *CORE::GLOBAL::length = sub (_) { + defined $_[0] ? CORE::length($_[0]) : undef + }; + } + *Irssi::active_win = {}; # hide incorrect warning +} + +unless (IN_IRSSI) { + local *_ = \@ARGV; + &AwlViewer::main; + exit; +} + + +use constant GLOB_QUEUE_TIMER => 100; + +our $BLOCK_ALL; # localized blocker +my @actString; # status bar texts +my @win_items; +my $currentLines = 0; +my %awins; +my $globTime; # timer to limit remake calls + +my %CHANGED; +my $VIEWER_MODE; +my $MOUSE_ON; +my %mouse_coords; +my %statusbars; +my %S; # settings +my $settings_str = '1'; +my $window_sort_func; +my $custom_xform; +my ($sb_base_width, $sb_base_width_pre, $sb_base_width_post); +my $print_text_activity; +my $shade_line_timer; +my ($screenHeight, $screenWidth); +my %viewer; + +my (%keymap, %nummap, %wnmap, %specialmap, %wnmap_exp, %custom_key_map); +my %banned_channels; +my %detach_map; +my %abbrev_cache; + +use constant setc => 'awl'; + +sub set ($) { + setc . '_' . $_[0] +} + +sub add_statusbar { + for (@_) { + # add subs + my $l = set $_; + { + my $close = $_; + no strict 'refs'; + *{$l} = sub { awl($close, @_) }; + } + Irssi::command("^statusbar $l reset"); + Irssi::command("statusbar $l enable"); + if (lc $S{placement} eq 'top') { + Irssi::command("statusbar $l placement top"); + } + if (my $x = $S{position}) { + Irssi::command("statusbar $l position $x"); + } + Irssi::command("statusbar $l add -priority 100 -alignment left barstart"); + Irssi::command("statusbar $l add $l"); + Irssi::command("statusbar $l add -priority 100 -alignment right barend"); + Irssi::command("statusbar $l disable"); + Irssi::statusbar_item_register($l, '$0', $l); + $statusbars{$_} = 1; + Irssi::command("statusbar $l enable"); + } +} + +sub remove_statusbar { + for (@_) { + my $l = set $_; + Irssi::command("statusbar $l disable"); + Irssi::command("statusbar $l reset"); + Irssi::statusbar_item_unregister($l); + { + no strict 'refs'; + undef &{$l}; + } + delete $statusbars{$_}; + } +} + +my $awl_shared_empty = sub { + return if $BLOCK_ALL; + my ($item, $get_size_only) = @_; + $item->default_handler($get_size_only, '', '', 0); +}; + +sub syncLines { + my $maxLines = $S{maxlines}; + my $newLines = ($maxLines > 0 and @actString > $maxLines) ? + $maxLines : + ($maxLines < 0) ? + -$maxLines : + @actString; + $currentLines = 1 if !$currentLines && $S{shared_sbar}; + if ($S{shared_sbar} && !$statusbars{shared}) { + my $l = set 'shared'; + { + no strict 'refs'; + *{$l} = sub { + return if $BLOCK_ALL; + my ($item, $get_size_only) = @_; + + my $text = $actString[0]; + my $title = _get_format(set 'title'); + if (length $title) { + $title =~ s{\\(.)|(.)}{ + defined $2 ? quotemeta $2 + : $1 eq 'V' ? '\u' + : $1 eq ':' ? quotemeta ':%n' + : $1 =~ /^[uUFQE]$/ ? "\\$1" + : quotemeta "\\$1" + }sge; + $title = eval qq{"$title"}; + $title .= ' '; + } + my $pat = defined $text ? "{sb $title\$*}" : '{sb }'; + $text //= ''; + $item->default_handler($get_size_only, $pat, $text, 0); + }; + } + $statusbars{shared} = 1; + remove_statusbar (0) if $statusbars{0}; + } + elsif ($statusbars{shared} && !$S{shared_sbar}) { + add_statusbar (0) if $currentLines && $newLines; + delete $statusbars{shared}; + my $l = set 'shared'; + { + no strict 'refs'; + *{$l} = $awl_shared_empty; + } + } + if ($currentLines == $newLines) { return; } + elsif ($newLines > $currentLines) { + add_statusbar ($currentLines .. ($newLines - 1)); + } + else { + remove_statusbar (reverse ($newLines .. ($currentLines - 1))); + } + $currentLines = $newLines; +} + +sub awl { + return if $BLOCK_ALL; + my ($line, $item, $get_size_only) = @_; + + my $text = $actString[$line]; + my $pat = defined $text ? '{sb $*}' : '{sb }'; + $text //= ''; + $item->default_handler($get_size_only, $pat, $text, 0); +} + +# remove old statusbars +{ my %killBar; + sub get_old_status { + my ($textDest, $cont, $cont_stripped) = @_; + if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) { + my $name = quotemeta(set ''); + if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = 1; } + Irssi::signal_stop; + } + } + sub killOldStatus { + %killBar = (); + Irssi::signal_add_first('print text' => 'get_old_status'); + Irssi::command('statusbar'); + Irssi::signal_remove('print text' => 'get_old_status'); + remove_statusbar(keys %killBar); + } +} + +sub _add_map { + my ($type, $target, $map) = @_; + ($type->{$target}) = sort { length $a <=> length $b || $a cmp $b } + $map, exists $type->{$target} ? $type->{$target} : (); +} + +sub get_keymap { + my ($textDest, undef, $cont_stripped) = @_; + if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) { + my $one_meta_or_ctrl_key = qr/((?:meta-)*?)(?:(meta-|\^)(\S)|(\w+))/; + $cont_stripped = as_uni($cont_stripped); + if ($cont_stripped =~ m/((?:$one_meta_or_ctrl_key-)*$one_meta_or_ctrl_key)\s+(.*)$/) { + my ($combo, $command) = ($1, $10); + my $map = ''; + while ($combo =~ s/(?:-|^)$one_meta_or_ctrl_key$//) { + my ($level, $ctl, $key, $nkey) = ($1, $2, $3, $4); + my $numlevel = ($level =~ y/-//); + $ctl = '' if !$ctl || $ctl ne '^'; + $map = ('-' x ($numlevel%2)) . ('+' x ($numlevel/2)) . + $ctl . (defined $key ? $key : "\01$nkey\01") . $map; + } + for ($command) { + last unless length $map; + if (/^change_window (\d+)/i) { + _add_map(\%nummap, $1, $map); + } + elsif (/^(?:command window goto|change_window) (\S+)/i) { + my $window = $1; + if ($window !~ /\D/) { + _add_map(\%nummap, $window, $map); + } + elsif (lc $window eq 'active') { + _add_map(\%specialmap, '_active', $map); + } + else { + _add_map(\%wnmap, $window, $map); + } + } + elsif (/^(?:active_window|command ((awl )?ack))/i) { + _add_map(\%specialmap, '_active', $map); + $viewer{use_ack} = $1; + } + elsif (/^command window last/i) { + _add_map(\%specialmap, '_last', $map); + } + elsif (/^(?:upper_window|command window up)/i) { + _add_map(\%specialmap, '_up', $map); + } + elsif (/^(?:lower_window|command window down)/i) { + _add_map(\%specialmap, '_down', $map); + } + elsif (/^key\s+(\w+)/i) { + $custom_key_map{$1} = $map; + } + } + } + Irssi::signal_stop; + } +} + +sub update_keymap { + %nummap = %wnmap = %specialmap = %custom_key_map = (); + Irssi::signal_remove('command bind' => 'watch_keymap'); + Irssi::signal_add_first('print text' => 'get_keymap'); + Irssi::command('bind'); + Irssi::signal_remove('print text' => 'get_keymap'); + for (keys %custom_key_map) { + if (exists $custom_key_map{$_} && + $custom_key_map{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) { + if ($custom_key_map{$_} =~ /\02/) { + delete $custom_key_map{$_}; + } + else { + redo; + } + } + } + for my $keymap (\(%specialmap, %wnmap, %nummap)) { + for (keys %$keymap) { + if ($keymap->{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) { + if ($keymap->{$_} =~ /\02/) { + delete $keymap->{$_}; + } + } + } + } + Irssi::signal_add('command bind' => 'watch_keymap'); + delete $viewer{client_keymap}; + &wl_changed; +} + +# watch keymap changes +sub watch_keymap { + Irssi::timeout_add_once(1000, 'update_keymap', undef); +} + +{ my %strip_table = ( + # fe-common::core::formats.c:format_expand_styles + # delete format_backs format_fores bold_fores other stuff + (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')), + # escape + (map { $_ => $_ } (split //, '{}%')), + ); + sub ir_strip_codes { # strip %codes + my $o = shift; + $o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} : + $2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex; + $o + } +} +## ir_parse_special -- wrapper around parse_special +## $i - input format +## $args - array ref of arguments to format +## $win - different target window (default current window) +## $flags - different kind of escape flags (default 4|8) +## returns formatted str +sub ir_parse_special { + my $o; + my $i = shift; + my $args = shift // []; + y/ /\177/ for @$args; # hack to escape spaces + my $win = shift || Irssi::active_win; + my $flags = shift // 0x4|0x8; + my @cmd_args = ($i, (join ' ', @$args), $flags); + my $server = Irssi::active_server(); + if (ref $win and ref $win->{active}) { + $o = $win->{active}->parse_special(@cmd_args); + } + elsif (ref $win and ref $win->{active_server}) { + $o = $win->{active_server}->parse_special(@cmd_args); + } + elsif (ref $server) { + $o = $server->parse_special(@cmd_args); + } + else { + $o = &Irssi::parse_special(@cmd_args); + } + $o =~ y/\177/ /; + $o +} + +sub sb_format_expand { # Irssi::current_theme->format_expand wrapper + Irssi::current_theme->format_expand( + $_[0], + ( + Irssi::EXPAND_FLAG_IGNORE_REPLACES + | + ($_[1] ? 0 : Irssi::EXPAND_FLAG_IGNORE_EMPTY) + ) + ) +} + +{ my $term_type = Irssi::version > 20040819 ? 'term_charset' : 'term_type'; + if (Irssi->can('string_width')) { + *screen_length = sub { Irssi::string_width($_[0]) }; + } + else { + local $@; + eval { require Text::CharWidth; }; + unless ($@) { + *screen_length = sub { Text::CharWidth::mbswidth($_[0]) }; + } + else { + my $err = $@; chomp $err; $err =~ s/\sat .* line \d+\.$//; + #Irssi::print("%_$IRSSI{name}: warning:%_ Text::CharWidth module failed to load. Length calculation may be off! Error was:"); + print "%_$IRSSI{name}:%_ $err"; + *screen_length = sub { + my $temp = shift; + if (lc Irssi::settings_get_str($term_type) eq 'utf-8') { + Encode::_utf8_on($temp); + } + length($temp) + }; + } + } + sub as_uni { + no warnings 'utf8'; + Encode::decode(Irssi::settings_get_str($term_type), $_[0], 0) + } + sub as_tc { + Encode::encode(Irssi::settings_get_str($term_type), $_[0], 0) + } +} + +sub sb_length { + screen_length(ir_strip_codes($_[0])) +} + +sub run_custom_xform { + local $@; + eval { + $custom_xform->() + }; + if ($@) { + $@ =~ /^(.*)/; + print '%_'.(set 'custom_xform').'%_ died (disabling): '.$1; + $custom_xform = undef; + } +} + +sub remove_uniform { + my $o = shift; + $o =~ s/^xmpp:(.*?[%@]).+\.[^.]+$/$1/ or + $o =~ s#^psyc://.+\.[^.]+/([@~].*)$#$1#; + if ($custom_xform) { + run_custom_xform() for $o; + } + $o +} + +sub remove_uniform_vars { + my $win = shift; + my $name = __PACKAGE__ . '::custom_xform::' . $win->{active}{type} + if ref $win->{active} && $win->{active}{type}; + no strict 'refs'; + local ${$name} = 1 if $name; + remove_uniform(+shift); +} + +sub lc1459 { + my $x = shift; + $x =~ y/][\\^/}{|~/; + lc $x +} + +sub window_list { + my $i = 0; + map { $_->[1] } sort $window_sort_func map { [ $i++, $_ ] } Irssi::windows; +} + +sub _calculate_abbrev { + my ($wins, $abbrevList) = @_; + if ($S{fancy_abbrev} !~ /^(no|off|head)/i) { + my @nameList = map { ref $_ ? remove_uniform_vars($_, as_uni($_->get_active_name) // '') : '' } @$wins; + for (my $i = 0; $i < @nameList - 1; ++$i) { + my ($x, $y) = ($nameList[$i], $nameList[$i + 1]); + s/^[+#!=]// for $x, $y; + my $res = exists $abbrev_cache{$x}{$y} ? $abbrev_cache{$x}{$y} + : $abbrev_cache{$x}{$y} = string_LCSS($x, $y); + if (defined $res) { + for ($nameList[$i], $nameList[$i + 1]) { + $abbrevList->{$_} //= int((index $_, $res) + (length $res) / 2); + } + } + } + } +} + +my %act_last_line_shades = ( + r => [qw[ 50 40 30 20 ]], + g => [qw[ 1O 1I 1C 16 ]], + y => [qw[ 5O 4I 3C 26 ]], + b => [qw[ 15 14 13 12 ]], + m => [qw[ 54 43 32 21 ]], + c => [qw[ 1S 1L 1E 17 ]], + w => [qw[ 7W 7T 7Q 3E ]], + K => [qw[ 7M 7K 27 7H ]], + R => [qw[ 60 50 40 30 ]], + G => [qw[ 1U 1O 1I 1C ]], + Y => [qw[ 6U 5O 4I 3C ]], + B => [qw[ 2B 2A 29 28 ]], + M => [qw[ 65 54 43 32 ]], + C => [qw[ 1Z 1S 1L 1E ]], + W => [qw[ 6Z 5S 7R 7O ]], + ); + +sub _format_display { + my (undef, $format, $cformat, $hilight, $name, $number, $key, $win) = @_; + if ($print_text_activity && $S{line_shade}) { + my @hilight_code = split /\177/, sb_format_expand("{$hilight \177}"), 2; + my $max_time = max(1, log($S{line_shade}) - log(1000)); + my $time_delta = min(3, min($max_time, log(max(1, time - $win->{last_line}))) / $max_time * 3); + if ($hilight_code[0] =~ /%(.)/ && exists $act_last_line_shades{$1}) { + $hilight = 'sb_act_hilight_color %X'.$act_last_line_shades{$1}[$time_delta]; + } + } + $cformat = '$0' unless length $cformat; + my %map = ('$C' => $cformat, '$N' => '$1', '$Q' => '$2'); + $format =~ s<(\$.)><$map{$1}//$1>ge; + $format =~ s<\$H((?:\$.|[^\$])*?)\$S><{$hilight $1%n}>g; + my @ret = ir_parse_special(sb_format_expand($format), [$name, $number, $key], $win); + @ret +} + +sub _get_format { + Irssi::current_theme->get_format(__PACKAGE__, @_) +} + +sub _is_detached { + my ($win, $active_number) = @_; + my $level = $win->{data_level} // 0; + my $number = $win->{refnum}; + my $name = lc1459( as_uni($win->{name}) ); + my $active = lc1459( as_uni($win->get_active_name) // '' ); + my $tag = $win->{active} && $win->{active}{server} ? lc1459( as_uni($win->{active}{server}{tag}) // '' ) : ''; + my @cond = ($number); + push @cond, "$name" if length $name; + push @cond, "$tag/$active" if length $tag && length $active; + push @cond, "$active" if length $active; + push @cond, "$tag/*", "$tag/::all" if length $tag; + push @cond, "*", "::all"; + for my $cond (@cond) { + if (exists $detach_map{ $cond }) { + my $dd = $detach_map{ $cond } // $S{detach_data}; + return $win->{data_level} < abs $dd + && ($number != $active_number || 0 <= $dd); + } + } + return; +} + +sub _calculate_items { + my ($wins, $abbrevList) = @_; + + my $display_header = _get_format(set 'display_header'); + my $name_format = _get_format(set 'name_display'); + my $abbrev_chars = as_uni(_get_format(set 'abbrev_chars')); + + my %displays; + + my $active = Irssi::active_win; + @win_items = (); + %keymap = (%nummap, %wnmap_exp); + + my ($numPad, $keyPad) = (0, 0); + if ($VIEWER_MODE or $S{block} < 0) { + $numPad = length((sort { length $b <=> length $a } keys %keymap)[0]) // 0; + $keyPad = length((sort { length $b <=> length $a } values %keymap)[0]) // 0; + } + my $last_net; + my ($abbrev1, $abbrev2) = $abbrev_chars =~ /(\X)(.*)/; + my @abbrev_chars = ('~', "\x{301c}"); + unless (defined $abbrev1 && screen_length(as_tc($abbrev1)) == 1) { $abbrev1 = $abbrev_chars[0] } + unless (length $abbrev2) { + $abbrev2 = $abbrev1; + if ($abbrev1 eq $abbrev_chars[0]) { + $abbrev2 = $abbrev_chars[1]; + } + else { + $abbrev2 = $abbrev1; + } + } + if (screen_length(as_tc($abbrev2)) == 1) { + $abbrev2 x= 2; + } + while (screen_length(as_tc($abbrev2)) > 2) { + chop $abbrev2; + } + unless (screen_length(as_tc($abbrev2)) == 2) { + $abbrev2 = $abbrev_chars[1]; + } + for my $win (@$wins) { + my $global_tag_header_mode; + + next unless ref $win; + + my $backup_win = Storable::dclone($win); + delete $backup_win->{active} unless ref $backup_win->{active}; + + $global_tag_header_mode = + $display_header && ($last_net // '') ne ($backup_win->{active}{server}{tag} // ''); + + if ($win->{data_level} < abs $S{hide_data} + && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_data})) { + next; } + elsif (exists $awins{$win->{refnum}} && $S{hide_empty} && !$win->items + && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_empty})) { + next; } + elsif (_is_detached($win, $active->{refnum})) { + next; } + + my $colour = $win->{hilight_color} // ''; + my $hilight = do { + if ($win->{data_level} == 0) { 'sb_act_none'; } + elsif ($win->{data_level} == 1) { 'sb_act_text'; } + elsif ($win->{data_level} == 2) { 'sb_act_msg'; } + elsif ($colour ne '') { "sb_act_hilight_color $colour"; } + elsif ($win->{data_level} == 3) { 'sb_act_hilight'; } + else { 'sb_act_special'; } + }; + my $number = $win->{refnum}; + + my ($name, $display, $cdisplay); + if ($global_tag_header_mode) { + $display = $display_header; + $name = as_uni($backup_win->{active}{server}{tag}) // ''; + if ($custom_xform) { + no strict 'refs'; + local ${ __PACKAGE__ . '::custom_xform::TAG' } = 1; + run_custom_xform() for $name; + } + } + else { + my @display = ('display_nokey'); + if (defined $keymap{$number} and $keymap{$number} ne '') { + unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display; + } + if (exists $awins{$number}) { + unshift @display, map { my $cpy = $_; $cpy .= '_visible'; $cpy } @display; + } + if ($active->{refnum} == $number) { + unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } + grep { !/_visible$/ } @display; + } + $display = (grep { length $_ } + map { $displays{$_} //= _get_format(set $_) } + @display)[0]; + $cdisplay = $name_format; + $name = as_uni($win->get_active_name) // ''; + $name = '*' if $S{banned_on} and exists $banned_channels{lc1459($name)}; + $name = remove_uniform_vars($win, $name) if $name ne '*'; + if ($name ne '*' and $win->{name} ne '' and $S{prefer_name}) { + $name = as_uni($win->{name}); + if ($custom_xform) { + no strict 'refs'; + local ${ __PACKAGE__ . '::custom_xform::NAME' } = 1; + run_custom_xform() for $name; + } + } + + if (!$VIEWER_MODE && $S{block} >= 0 && $S{hide_name} + && $win->{data_level} < abs $S{hide_name} + && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_name})) { + $name = ''; + $cdisplay = ''; + } + } + + $display = "$display%n"; + my $num_ent = (' 'x max(0,$numPad - length $number)) . $number; + my $key_ent = exists $keymap{$number} ? ((' 'x max(0,$keyPad - length $keymap{$number})) . $keymap{$number}) : ' 'x$keyPad; + if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) { + my $baseLength = sb_length(_format_display( + '', $display, $cdisplay, $hilight, + 'x', # placeholder + $num_ent, + $key_ent, + $win)) - 1; + my $diff = (abs $S{block}) - (screen_length(as_tc($name)) + $baseLength); + if ($diff < 0) { # too long + my $screen_length = screen_length(as_tc($name)); + if ((abs $diff) >= $screen_length) { $name = '' } # forget it + elsif ((abs $diff) + screen_length(as_tc(substr($name, 0, 1))) >= $screen_length) { $name = substr($name, 0, 1); } + else { + my $ulen = length $name; + my $middle2 = exists $abbrevList->{$name} ? + ($S{fancy_strict}) ? + 2* $abbrevList->{$name} : + (2*($abbrevList->{$name} + $ulen) / 3) : + ($S{fancy_head}) ? + 2*$ulen : + $ulen; + my $first = 1; + while (length $name > 1) { + my $cp = $middle2 >= 0 ? $middle2/2 : -1; # clearing position + my $rm = 2; + # if character at end is wider than 1 cell -> replace it with ~ + if (screen_length(as_tc(substr $name, $cp, 1)) > 1) { + if ($first || $cp < 0) { + $rm = 1; + $first = undef; + } + } + elsif ($cp < 0) { # elsif at end -> replace last 2 characters + --$cp; + } + (substr $name, $cp, $rm) = $abbrev1; + if ($cp > -1 && $rm > 1) { + --$middle2; + } + my $sl = screen_length(as_tc($name)); + if ($sl + $baseLength < abs $S{block}) { + (substr $name, ($middle2+1)/2, 1) = $abbrev2; + last; + } + elsif ($sl + $baseLength == abs $S{block}) { + last; + } + } + } + } + elsif ($VIEWER_MODE or $S{block} < 0) { + $name .= (' ' x $diff); + } + } + + push @win_items, _format_display( + '', $display, $cdisplay, $hilight, + as_tc($name), + $num_ent, + as_tc($key_ent), + $win); + + if ($global_tag_header_mode) { + $last_net = $backup_win->{active}{server}{tag}; + redo; + } + + $mouse_coords{refnum}{$#win_items} = $number; + } +} + +sub _spread_items { + my $width = $screenWidth - $sb_base_width - 1; + my @separator = _get_format(set 'separator'); + if ($S{block} >= 0) { + my $sep2 = _get_format(set 'separator2'); + push @separator, $sep2 if length $sep2 && $sep2 ne $separator[0]; + } + $separator[0] .= '%n'; + my @sepLen = map { sb_length($_) } @separator; + + @actString = (); + my $curLine; + my $curLen = 0; + if ($S{shared_sbar}) { + $curLen += $S{shared_sbar}[0] + 2; + $width -= $S{shared_sbar}[2]; + } + my $mouse_header_check = 0; + for my $it (@win_items) { + my $itemLen = sb_length($it); + if ($curLen) { + if ($curLen + $itemLen + $sepLen[$mouse_header_check % @sepLen] > $width) { + $width += $S{shared_sbar}[2] + if !@actString && $S{shared_sbar}; + push @actString, $curLine; + $curLine = undef; + $curLen = 0; + } + elsif (defined $curLine) { + $curLine .= $separator[$mouse_header_check % @separator]; + $curLen += $sepLen[$mouse_header_check % @sepLen]; + } + } + $curLine .= $it; + if (exists $mouse_coords{refnum}{$mouse_header_check}) { + $mouse_coords{scalar @actString}{ $_ } = $mouse_coords{refnum}{$mouse_header_check} + for $curLen .. $curLen + $itemLen - 1; + } + $curLen += $itemLen; + } + continue { + ++$mouse_header_check; + } + $curLen -= $S{shared_sbar}[0] + if !@actString && $S{shared_sbar}; + push @actString, $curLine if $curLen; +} + +sub remake { + my %abbrevList; + my @wins = window_list(); + if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) { + _calculate_abbrev(\@wins, \%abbrevList); + } + + %mouse_coords = ( refnum => +{} ); + _calculate_items(\@wins, \%abbrevList); + + unless ($VIEWER_MODE) { + _spread_items(); + + push @actString, undef unless @actString || $S{all_disable}; + } +} + +sub update_wl { + return if $BLOCK_ALL; + remake(); + + Irssi::statusbar_items_redraw(set $_) for keys %statusbars; + + unless ($VIEWER_MODE) { + Irssi::timeout_add_once(100, 'syncLines', undef); + } + else { + syncViewer(); + } +} + +sub screenFullRedraw { + my ($window) = @_; + if (!ref $window or $window->{refnum} == Irssi::active_win->{refnum}) { + $viewer{fullRedraw} = 1 if $viewer{client}; + $settings_str = ''; + &setup_changed; + } +} + +sub restartViewerServer { + if ($VIEWER_MODE) { + stop_viewer(); + start_viewer(); + } +} + +sub _simple_quote { + my @r = map { + my $x = $_; + $x =~ s/'/'"'"'/g; + $x = "'$x'"; + } @_; + wantarray ? @r : shift @r +} + +sub _viewer_command_replace_format { + my ($ecmd, @args) = @_; + my $file = _simple_quote(SCRIPT_FILE()); + my $path = _simple_quote($viewer{path}); + my @env; + for my $env (shellwords($S{viewer_launch_env})) { + if ($env =~ /^(\w+)(?:=(.*))$/) { + push @env, "AWL_$1=$2" + } + } + my $cmd = join ' ', + (@env ? ('env', _simple_quote(@env)) : ()), + 'perl', $file, '-1', _simple_quote(@args), $path; + $ecmd =~ s{%(%|\w+)}{ + my $sub = $1; + if ($sub eq '%') { + '%' + } + elsif ($sub =~ /^(q*)A(.*)/) { + my $ret = $cmd; + for (1..length $1) { + $ret = _simple_quote($ret); + } + "$ret$2" + } + else { + "%$sub" + } + }gex; + $ecmd +} + +sub start_viewer { + unlink $viewer{path} if -S $viewer{path} || -p _; + + $viewer{server} = IO::Socket::UNIX->new( + Type => SOCK_STREAM, + Local => $viewer{path}, + Listen => 1 + ); + unless ($viewer{server}) { + $viewer{msg} = "Viewer: $!"; + $viewer{retry} = Irssi::timeout_add_once(5000, 'retry_viewer', 1); + return; + } + $viewer{server}->blocking(0); + set_viewer_mode_hint(); + $viewer{server_tag} = Irssi::input_add($viewer{server}->fileno, INPUT_READ, 'vi_connected', undef); + + if ($S{viewer_launch}) { + if (length $ENV{TMUX_PANE} && length $ENV{TMUX} && lc $S{viewer_tmux_position} ne 'custom') { + my $cmd = _viewer_command_replace_format('%qA', '-p', lc $S{viewer_tmux_position}); + Irssi::command("exec - tmux neww -d $cmd 2>&1 &"); + } + elsif (length $ENV{WINDOWID} && length $ENV{DISPLAY} && length $S{viewer_xwin_command} && $S{viewer_xwin_command} =~ /\S/) { + my $cmd = _viewer_command_replace_format($S{viewer_xwin_command}); + Irssi::command("exec - $cmd 2>&1 &"); + } + elsif (length $S{viewer_custom_command} && $S{viewer_custom_command} =~ /\S/) { + my $cmd = _viewer_command_replace_format($S{viewer_custom_command}); + Irssi::command("exec - $cmd 2>&1 &"); + } + } +} + +sub set_viewer_mode_hint { + return unless $viewer{server}; + if ($S{no_mode_hint}) { + $viewer{msg} = undef; + } + else { + my ($name) = __PACKAGE__ =~ /::([^:]+)$/; + $viewer{msg} = "Run $name from the shell or switch to sbar mode"; + } +} + +sub retry_viewer { + start_viewer(); +} + +sub vi_close_client { + Irssi::input_remove(delete $viewer{client_tag}) if exists $viewer{client_tag}; + $viewer{client}->close if $viewer{client}; + delete $viewer{client}; + delete $viewer{client_keymap}; + delete $viewer{client_settings}; + delete $viewer{client_env}; + delete $viewer{fullRedraw}; +} + +sub vi_connected { + vi_close_client(); + $viewer{client} = $viewer{server}->accept or return; + $viewer{client}->blocking(0); + $viewer{client_tag} = Irssi::input_add($viewer{client}->fileno, INPUT_READ, 'vi_clientinput', undef); + syncViewer(); +} + +use constant VIEWER_BLOCK_SIZE => 1024; +sub vi_clientinput { + if ($viewer{client}->read(my $buf, VIEWER_BLOCK_SIZE)) { + $viewer{rcvbuf} .= $buf; + if ($viewer{rcvbuf} =~ s/^(?:(active|\d+)|(last|up|down))\n//igm) { + if (defined $2) { + Irssi::command("window $2"); + } + elsif (lc $1 eq 'active' && $viewer{use_ack}) { + Irssi::command($viewer{use_ack}); + } + else { + Irssi::command("window goto $1"); + } + } + } + else { + vi_close_client(); + Irssi::timeout_add_once(100, 'syncViewer', undef); + } +} + +sub stop_viewer { + Irssi::timeout_remove(delete $viewer{retry}) if exists $viewer{retry}; + vi_close_client(); + Irssi::input_remove(delete $viewer{server_tag}) if exists $viewer{server_tag}; + return unless $viewer{server}; + $viewer{server}->close; + delete $viewer{server}; +} +sub _encode_var { + my $str; + while (@_) { + my ($name, $var) = splice @_, 0, 2; + my $type = ref $var ? $var =~ /HASH/ ? 'map' : $var =~ /ARRAY/ ? 'list' : '' : ''; + $str .= "\n\U$name$type\_begin\n"; + if ($type eq 'map') { + no warnings 'numeric'; + $str .= " $_\n ${$var}{$_}\n" for sort { $a <=> $b || $a cmp $b } keys %$var; + } + elsif ($type eq 'list') { + $str .= " $_\n" for @$var; + } + else { + $str .= " $var\n"; + } + $str .= "\U$name$type\_end\n"; + } + $str +} +sub syncViewer { + if ($viewer{client}) { + @actString = (); + if ($currentLines) { + killOldStatus(); + $currentLines = 0; + } + my $str; + unless ($viewer{client_keymap}) { + $str .= _encode_var('key', +{ %nummap, %specialmap }); + $viewer{client_keymap} = 1; + } + unless ($viewer{client_settings}) { + $str .= _encode_var( + block => $S{block}, + ha => $S{height_adjust}, + mc => $S{maxcolumns}, + ml => $S{maxlines}, + ); + $viewer{client_settings} = 1; + } + unless ($viewer{client_env}) { + $str .= _encode_var(irssienv => +{ + length $ENV{TMUX_PANE} && length $ENV{TMUX} ? + (tmux_pane => $ENV{TMUX_PANE}, + tmux_srv => $ENV{TMUX}) : (), + length $ENV{WINDOWID} ? + (xwinid => $ENV{WINDOWID}) : (), + }); + $viewer{client_env} = 1; + } + my $separator = _get_format(set 'separator'); + my $sepLen = sb_length($separator); + my $item_bg = _get_format(set 'viewer_item_bg'); + my $title = _get_format(set 'title'); + if (length $title) { + $title =~ s{\\(.)|(.)}{ + defined $2 ? quotemeta $2 + : $1 eq 'V' ? '\U' + : $1 eq ':' ? quotemeta '%N' + : $1 =~ /^[uUFQE]$/ ? "\\$1" + : quotemeta "\\$1" + }sge; + $title = eval qq{"$title"}; + } + $str .= _encode_var(redraw => 1) if delete $viewer{fullRedraw}; + $str .= _encode_var(separator => $separator, + seplen => $sepLen, + itembg => $item_bg, + title => $title, + mouse => $mouse_coords{refnum}, + key2 => \%wnmap_exp, + win => \@win_items); + + my $was = $viewer{client}->blocking(1); + $viewer{client}->print($str); + $viewer{client}->blocking($was); + } + elsif ($viewer{server}) { + if (defined $viewer{msg}) { + @actString = ((uc setc()).": $viewer{msg}"); + } + else { + @actString = (); + } + } + elsif (defined $viewer{msg}) { + @actString = ((uc setc()).": $viewer{msg}"); + } + if (@actString) { + Irssi::timeout_add_once(100, 'syncLines', undef); + } + elsif ($currentLines) { + killOldStatus(); + $currentLines = 0; + } +} + +sub reset_awl { + Irssi::timeout_remove($shade_line_timer) if $shade_line_timer; $shade_line_timer = undef; + my $was_sort = $S{sort} // ''; + my $was_xform = $S{xform} // ''; + my $was_shared = $S{shared_sbar}; + my $was_no_hint = $S{no_mode_hint}; + %S = ( + sort => Irssi::settings_get_str( set 'sort'), + fancy_abbrev => Irssi::settings_get_str('fancy_abbrev'), + xform => Irssi::settings_get_str( set 'custom_xform'), + block => Irssi::settings_get_int( set 'block'), + banned_on => Irssi::settings_get_bool('banned_channels_on'), + prefer_name => Irssi::settings_get_bool(set 'prefer_name'), + hide_data => Irssi::settings_get_int( set 'hide_data'), + hide_name => Irssi::settings_get_int( set 'hide_name_data'), + hide_empty => Irssi::settings_get_int( set 'hide_empty'), + detach => Irssi::settings_get_str( set 'detach'), + detach_data => Irssi::settings_get_int( set 'detach_data'), + detach_aht => Irssi::settings_get_bool(set 'detach_aht'), + sbar_maxlen => Irssi::settings_get_bool(set 'sbar_maxlength'), + placement => Irssi::settings_get_str( set 'placement'), + position => Irssi::settings_get_int( set 'position'), + maxlines => Irssi::settings_get_int( set 'maxlines'), + maxcolumns => Irssi::settings_get_int( set 'maxcolumns'), + all_disable => Irssi::settings_get_bool(set 'all_disable'), + height_adjust => Irssi::settings_get_int( set 'height_adjust'), + mouse_offset => Irssi::settings_get_int( set 'mouse_offset'), + mouse_scroll => Irssi::settings_get_int( 'mouse_scroll'), + mouse_escape => Irssi::settings_get_int( 'mouse_escape'), + line_shade => Irssi::settings_get_time(set 'last_line_shade'), + no_mode_hint => Irssi::settings_get_bool(set 'no_mode_hint'), + viewer_launch => Irssi::settings_get_bool(set 'viewer_launch'), + viewer_launch_env => Irssi::settings_get_str(set 'viewer_launch_env'), + viewer_xwin_command => Irssi::settings_get_str(set 'viewer_xwin_command'), + viewer_custom_command => Irssi::settings_get_str(set 'viewer_custom_command'), + viewer_tmux_position => Irssi::settings_get_str(set 'viewer_tmux_position'), + ); + $S{fancy_strict} = $S{fancy_abbrev} =~ /^strict/i; + $S{fancy_head} = $S{fancy_abbrev} =~ /^head/i; + my $shared = Irssi::settings_get_str(set 'shared_sbar'); + if ($shared =~ /^(\d+)([<])(\d+)$/) { + $S{shared_sbar} = [$1, $2, $3]; + } + else { + Irssi::settings_set_str(set 'shared_sbar', 'OFF'); + $S{shared_sbar} = undef; + } + lock_keys(%S); + if ($was_sort ne $S{sort}) { + $print_text_activity = undef; + my @sort_order = grep { @$_ > 4 } map { + s/^\s*//; + my $reverse = s/^\W*\K[-!]//; + my $undef_check = s/^\W*\K~// ? 1 : undef; + my $equal_check = s/=(.*)\s?$// ? $1 : undef; + s/\s*$//; + my $ignore_case = s/#i$// ? 1 : undef; + + $print_text_activity = 1 if $_ eq 'last_line'; + + my @path = split '/'; + my $class_check = @path && $path[-1] =~ s/(::.*)$// ? $1 : undef; + my $lru = "@path" eq 'lru'; + + [ $reverse ? -1 : 1, $undef_check, $equal_check, $class_check, $ignore_case, $lru, @path ] + } "$S{sort}," =~ /([^+,]*|[^+,]*=[^,]*?\s(?=\+)|[^+,]*=[^,]*)[+,]/g; + $window_sort_func = sub { + no warnings qw(numeric uninitialized); + for my $so (@sort_order) { + my @x = map { + my $ret = 0; + $_ = lc1459($_) if defined $_ && !ref $_ && $so->[4]; + $ret = $_ eq ($so->[4] ? lc1459($so->[2]) : $so->[2]) ? 1 : -1 if defined $so->[2]; + $ret = defined $_ ? ($ret || -3) : 3 if $so->[1]; + $ret = ref $_ && $_->isa('Irssi'.$so->[3]) ? 2 : ($ret || -2) if $so->[3]; + -$ret || $_ + } + map { + $so->[5] ? $_->[0] : reduce { return unless ref $a; $a->{$b} } $_->[1], @{$so}[6..$#$so] + } $a, $b; + return ((($x[0] <=> $x[1] || $x[0] cmp $x[1]) * $so->[0]) || next); + } + return ($a->[1]{refnum} <=> $b->[1]{refnum}); + }; + } + if ($was_xform ne $S{xform}) { + if ($S{xform} !~ /\S/) { + $custom_xform = undef; + } + else { + my $script_pkg = __PACKAGE__ . '::custom_xform'; + local $@; + $custom_xform = eval qq{ +package $script_pkg; +use strict; +no warnings; +our (\$QUERY, \$CHANNEL, \$TAG, \$NAME); +return sub { +# line 1 @{[ set 'custom_xform' ]}\n$S{xform}\n}}; + if ($@) { + $@ =~ /^(.*)/; + print '%_'.(set 'custom_xform').'%_ did not compile: '.$1; + } + } + } + + my $new_settings = join "\n", $VIEWER_MODE + ? ("\\", $S{block}, $S{height_adjust}, $S{maxlines}, $S{maxcolumns}) + : ("!", $S{placement}, $S{position}); + + my $first_viewer = $settings_str eq '1'; + if ($settings_str ne $new_settings) { + @actString = (); + %abbrev_cache = (); + $currentLines = 0; + killOldStatus(); + delete $viewer{client_settings}; + $settings_str = $new_settings; + } + + my $was_mouse_mode = $MOUSE_ON; + if ($MOUSE_ON = Irssi::settings_get_bool(set 'mouse') and !$was_mouse_mode) { + install_mouse(); + } + elsif ($was_mouse_mode and !$MOUSE_ON) { + uninstall_mouse(); + } + + unless ($first_viewer) { + my $path = Irssi::settings_get_str(set 'path'); + my $was_viewer_mode = $VIEWER_MODE; + if ($was_viewer_mode && + defined $viewer{path} && $viewer{path} ne $path) { + stop_viewer(); + $was_viewer_mode = 0; + } + elsif ($was_viewer_mode && $S{no_mode_hint} != $was_no_hint + 0) { + set_viewer_mode_hint(); + } + $viewer{path} = $path; + if ($VIEWER_MODE = Irssi::settings_get_bool(set 'viewer') and !$was_viewer_mode) { + start_viewer(); + } + elsif ($was_viewer_mode and !$VIEWER_MODE) { + stop_viewer(); + } + } + + %banned_channels = map { lc1459(as_uni($_)) => undef } + split ' ', Irssi::settings_get_str('banned_channels'); + + %detach_map = ($S{detach_aht} + ? (map { ( lc1459(as_uni($_)) => undef ) } + split ' ', Irssi::settings_get_str('activity_hide_targets')) : (), + (map { my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1]; + ( lc1459(as_uni($k)) => $v ) } + split ' ', $S{detach})); + + my @sb_base = split /\177/, sb_format_expand("{sbstart}{sb \177}{sbend}"), 2; + $sb_base_width_pre = sb_length($sb_base[0]); + $sb_base_width_post = max 0, sb_length($sb_base[1])-1; + $sb_base_width = $sb_base_width_pre + $sb_base_width_post; + + if ($print_text_activity && $S{line_shade}) { + $shade_line_timer = Irssi::timeout_add(max(10 * GLOB_QUEUE_TIMER, 100*$S{line_shade}**(1/3)), 'wl_changed', undef); + } + + $CHANGED{AWINS} = 1; +} + +sub hide_window { + my ($data) = @_; + my $ent; + + $data =~ s/\s*$//; + my $win = Irssi::active_win; + my $number = $win->{refnum}; + my $name = as_uni($win->{name}); + my $active = as_uni($win->get_active_name) // ''; + my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : ''; + if (length $name) { + $ent = "$name"; + } + elsif (length $tag && length $active) { + $ent = "$tag/$active"; + } + else { + $ent = "$number"; + } + + my $found = 0; + my @setting; + for my $s (split ' ', $S{detach}) { + my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1]; + if (lc1459(as_uni($k)) eq lc1459($ent)) { + unless ($found) { + if ($data =~ /^(-?\d+)$/) { + $ent .= ",$1"; + } + if (defined $v && 0 == abs $v) { + $win->print("Hiding window $ent"); + } + push @setting, as_tc($ent); + $found = 1; + } + } + else { + push @setting, defined $v ? "$k,$v" : $k; + } + } + unless ($found) { + $win->print("Hiding window $ent"); + if ($data =~ /^(-?\d+)$/) { + $ent .= ",$1"; + } + push @setting, as_tc($ent); + } + + if (@setting) { + Irssi::command("^set ".(set 'detach')." @setting"); + } else { + Irssi::command("^set -clear ".(set 'detach')); + } +} + +sub unhide_window { + my ($data, $server, $witem) = @_; + my $win = Irssi::active_win; + my $number = $win->{refnum}; + my $name = as_uni($win->{name}); + my $active = as_uni($win->get_active_name) // ''; + my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : ''; + + my %detach_aht; + if ($S{detach_aht}) { + %detach_aht = (map { ( lc1459(as_uni($_)) => undef ) } + split ' ', Irssi::settings_get_str('activity_hide_targets')); + } + my @setting; + my @kills = (length $name ? $name : undef, + length $tag && length $active ? "$tag/$active" : undef, + length $active ? $active : undef, + $number); + my @was_unhidden = (0) x @kills; + for my $s (split ' ', $S{detach}) { + my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1]; + my $k2 = lc1459(as_uni($k)); + my $kill; + for my $ki (0..$#kills) { + if (defined $kills[$ki] && $k2 eq lc1459($kills[$ki])) { + $kill = $ki; + } + } + + if (defined $kill) { + if (defined $v && 0 == abs $v) { + $was_unhidden[$kill] = 1; + push @setting, defined $v ? "$k,$v" : $k; + } else { + $win->print("Unhiding window $kills[$kill]"); + } + } + else { + push @setting, defined $v ? "$k,$v" : $k; + } + } + my @is_hidden = (defined $kills[0] && (exists $detach_map{"*"} || exists $detach_map{"::all"}), + defined $kills[1] && (exists $detach_map{lc1459("$tag/*")} || exists $detach_map{lc1459("$tag/::all")} + || exists $detach_map{"*"} || exists $detach_map{"::all"}), + defined $kills[2] && (exists $detach_map{"*"} || exists $detach_map{"::all"}), + (exists $detach_map{"*"} || exists $detach_map{"::all"}) + ); + for my $ki (1, 2, 0, 3) { + if ($is_hidden[$ki]) { + unless ($was_unhidden[$ki]) { + $win->print("Unhiding window $kills[$ki]"); + push @setting, "$kills[$ki],0"; + $was_unhidden[$ki] = 1; + } + last; + } + } + my @is_hidden_aht = (defined $kills[0] && (exists $detach_aht{lc1459($name)} + || exists $detach_aht{"*"} || exists $detach_aht{"::all"}), + defined $kills[1] && (exists $detach_aht{lc1459("$tag/$active")} + || exists $detach_aht{lc1459($active)} + || exists $detach_aht{lc1459("$tag/*")} || exists $detach_aht{lc1459("$tag/::all")} + || exists $detach_aht{"*"} || exists $detach_aht{"::all"}), + defined $kills[2] && (exists $detach_aht{lc1459($active)} + || exists $detach_aht{"*"} || exists $detach_aht{"::all"}), + (exists $detach_aht{$number} || exists $detach_aht{"*"} || exists $detach_aht{"::all"}) + ); + for my $ki (1, 2, 0, 3) { + if ($is_hidden_aht[$ki]) { + unless ($was_unhidden[$ki]) { + $win->print("Unhiding window $kills[$ki], it is hidden because ".(set 'detach_aht')." is ON"); + push @setting, "$kills[$ki],0"; + $was_unhidden[$ki] = 1; + } + last; + } + } + + if (@setting) { + Irssi::command("^set ".(set 'detach')." @setting"); + } else { + Irssi::command("^set -clear ".(set 'detach')); + } +} + +sub ack_window { + my ($data, $server, $witem) = @_; + my $win = Irssi::active_win; + my $number = $win->{refnum}; + if (grep { $_->{cmd} eq 'ack' } Irssi::commands) { + my $Orig_Irssi_windows = \&Irssi::windows; + local *Irssi::windows = sub () { grep { !_is_detached($_, $number) } $Orig_Irssi_windows->() }; + Irssi::command("ack" . (length $data ? " $data" : "")); + } else { + my $ignore_refnum = Irssi::settings_get_bool('active_window_ignore_refnum'); + my $max_win; + my $max_act = 0; + my $max_ref = 0; + for my $rec (Irssi::windows) { + next if _is_detached($rec, $number); + + # ignore refnum + if ($ignore_refnum && + $rec->{data_level} > 0 && $max_act < $rec->{data_level}) { + $max_act = $rec->{data_level}; + $max_win = $rec; + } + + # windows with lower refnums break ties + elsif (!$ignore_refnum && + $rec->{data_level} > 0 && + ($rec->{data_level} > $max_act || + ($rec->{data_level} == $max_act && $rec->{refnum} < $max_ref))) { + $max_act = $rec->{data_level}; + $max_win = $rec; + $max_ref = $rec->{refnum}; + } + } + $max_win->set_active if defined $max_win; + } +} + +sub refnum_changed { + my ($win, $old_refnum) = @_; + my @old_setting = split ' ', $S{detach}; + my @setting = map { + my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1]; + if ($k eq $old_refnum) { + $win->{refnum} . (defined $v ? ",$v" : "") + } + else { + $_ + } + } @old_setting; + if ("@old_setting" ne "@setting") { + $S{detach} = "@setting"; + Irssi::settings_set_str(set 'detach', "@setting"); + &setup_changed; + } + else { + &wl_changed; + } +} + +sub window_destroyed { + my ($win) = @_; + my @old_setting = split ' ', $S{detach}; + my @setting = grep { + my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1]; + if ($k eq $win->{refnum}) { + 0; + } + else { + 1; + } + } @old_setting; + if ("@old_setting" ne "@setting") { + $S{detach} = "@setting"; + Irssi::settings_set_str(set 'detach', "@setting"); + &setup_changed; + } + else { + &awins_changed; + } +} + +sub stop_mouse_tracking { + print STDERR "\e[?1005l\e[?1000l"; +} +sub start_mouse_tracking { + print STDERR "\e[?1000h\e[?1005h"; +} +sub install_mouse { + Irssi::command_bind('mouse_xterm' => 'mouse_xterm'); + Irssi::command('^bind meta-[M command mouse_xterm'); + Irssi::signal_add_first('gui key pressed' => 'mouse_key_hook'); + start_mouse_tracking(); +} +sub uninstall_mouse { + stop_mouse_tracking(); + Irssi::signal_remove('gui key pressed' => 'mouse_key_hook'); + Irssi::command('^bind -delete meta-[M'); + Irssi::command_unbind('mouse_xterm' => 'mouse_xterm'); +} + +sub awl_mouse_event { + return if $VIEWER_MODE; + if ((($_[0] == 3 and $_[3] == 0) + || $_[0] == 64 || $_[0] == 65) and + $_[1] == $_[4] and $_[2] == $_[5]) { + my $top = lc $S{placement} eq 'top'; + my ($pos, $line) = @_[1 .. 2]; + unless ($top) { + $line -= $screenHeight; + $line += $currentLines; + $line += $S{mouse_offset}; + } + else { + $line -= $S{mouse_offset}; + } + $pos -= $sb_base_width_pre; + return if $line < 0 || $line >= $currentLines; + if ($_[0] == 64) { + Irssi::command('window up'); + } + elsif ($_[0] == 65) { + Irssi::command('window down'); + } + elsif (exists $mouse_coords{$line}{$pos}) { + my $win = $mouse_coords{$line}{$pos}; + Irssi::command('window ' . $win); + } + Irssi::signal_stop; + } +} + +sub mouse_scroll_event { + return unless $S{mouse_scroll}; + if (($_[3] == 64 or $_[3] == 65) and + $_[0] == $_[3] and $_[1] == $_[4] and $_[2] == $_[5]) { + my $cmd = 'scrollback goto ' . ($_[3] == 64 ? '-' : '+') . $S{mouse_scroll}; + Irssi::active_win->command($cmd); + Irssi::signal_stop; + } + elsif ($_[0] == 64 or $_[0] == 65) { + Irssi::signal_stop; + } +} + +sub mouse_escape { + return unless $S{mouse_escape} > 0; + if ($_[0] == 3) { + my $tm = $S{mouse_escape}; + $tm *= 1000 if $tm < 1000; + stop_mouse_tracking(); + Irssi::timeout_add_once($tm, 'start_mouse_tracking', undef); + Irssi::signal_stop; + } +} + +sub UNLOAD { + @actString = (); + killOldStatus(); + stop_viewer() if $VIEWER_MODE; + uninstall_mouse() if $MOUSE_ON; +} + +sub addPrintTextHook { # update on print text + return unless defined $^S; + return if $BLOCK_ALL; + return unless $print_text_activity; + return if $_[0]->{level} == 262144 and $_[0]->{target} eq '' + and !defined($_[0]->{server}); + &wl_changed; +} + +sub block_event_window_change { + Irssi::signal_stop; +} + +sub update_awins { + my @wins = Irssi::windows; + local $BLOCK_ALL = 1; + Irssi::signal_add_first('window changed' => 'block_event_window_change'); + my $bwin = + my $awin = Irssi::active_win; + my $lwin; + my $defer_irssi_broken_last; + unless ($wins[0]{refnum} == $awin->{refnum}) { + # special case: more than 1 last win, so /win last; + # /win last doesn't come back to the current window. eg. after + # connect & autojoin; we can't handle this situation, bail out + $defer_irssi_broken_last = 1; + } + else { + $awin->command('window last'); + $lwin = Irssi::active_win; + $lwin->command('window last'); + $defer_irssi_broken_last = $lwin->{refnum} == $bwin->{refnum}; + } + my $awin_counter = 0; + Irssi::signal_remove('window changed' => 'block_event_window_change'); + unless ($defer_irssi_broken_last) { + # we need to keep the fe-windows code running here + Irssi::signal_add_priority('window changed' => 'block_event_window_change', -99); + %awins = %wnmap_exp = (); + do { + Irssi::active_win->command('window up'); + $awin = Irssi::active_win; + $awins{$awin->{refnum}} = undef; + ++$awin_counter; + } until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins); + Irssi::signal_remove('window changed' => 'block_event_window_change'); + + Irssi::signal_add_first('window changed' => 'block_event_window_change'); + for my $key (keys %wnmap) { + next unless Irssi::window_find_name($key) || Irssi::window_find_item($key); + $awin->command("window goto $key"); + my $cwin = Irssi::active_win; + $wnmap_exp{ $cwin->{refnum} } = $wnmap{$key}; + $cwin->command('window last') + if $cwin->{refnum} != $awin->{refnum}; + } + for my $win (reverse @wins) { # restore original window order + Irssi::active_win->command('window '.$win->{refnum}); + } + $awin->command('window '.$lwin->{refnum}); # restore last win + Irssi::active_win->command('window last'); + Irssi::signal_remove('window changed' => 'block_event_window_change'); + } + $CHANGED{WL} = 1; +} + +sub resizeTerm { + if (defined (my $r = `stty size 2>/dev/null`)) { + ($screenHeight, $screenWidth) = split ' ', $r; + $CHANGED{SETUP} = 1; + } + else { + $CHANGED{SIZE} = 1; + } +} + +sub awl_refresh { + $globTime = undef; + resizeTerm() if delete $CHANGED{SIZE}; + reset_awl() if delete $CHANGED{SETUP}; + update_awins() if delete $CHANGED{AWINS}; + update_wl() if delete $CHANGED{WL}; +} + +sub termsize_changed { $CHANGED{SIZE} = 1; &queue_refresh; } +sub setup_changed { $CHANGED{SETUP} = 1; &queue_refresh; } +sub awins_changed { $CHANGED{AWINS} = 1; &queue_refresh; } +sub wl_changed { $CHANGED{WL} = 1; &queue_refresh; } + +sub window_changed { + &awins_changed if $_[1]; +} + +sub queue_refresh { + return if $BLOCK_ALL; + Irssi::timeout_remove($globTime) + if defined $globTime; # delay the update further + $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awl_refresh', undef); +} + +sub awl_init { + termsize_changed(); + setup_changed(); + update_keymap(); + Irssi::timeout_remove($globTime) + if defined $globTime; + awl_refresh(); + termsize_changed(); +} + +sub runsub { + my $cmd = shift; + sub { + my ($data, $server, $item) = @_; + Irssi::command_runsub($cmd, $data, $server, $item); + }; +} + +Irssi::signal_register({ + 'gui mouse' => [qw/int int int int int int/], + }); +{ my $broken_expandos = (Irssi::version >= 20081128 && Irssi::version < 20110210) + ? sub { my $x = shift; $x =~ s/\$\{cumode_space\}/ /; $x } : undef; + Irssi::theme_register([ + map { $broken_expandos ? $broken_expandos->($_) : $_ } + set 'display_nokey' => '$N${cumode_space}$H$C$S', + set 'display_key' => '$Q${cumode_space}$H$C$S', + set 'display_nokey_visible' => '%2$N${cumode_space}$H$C$S', + set 'display_key_visible' => '%2$Q${cumode_space}$H$C$S', + set 'display_nokey_active' => '%1$N${cumode_space}$H$C$S', + set 'display_key_active' => '%1$Q${cumode_space}$H$C$S', + set 'display_header' => '%8$C|${N}', + set 'name_display' => '$0', + set 'separator' => ' ', + set 'separator2' => '', + set 'abbrev_chars' => "~\x{301c}", + set 'viewer_item_bg' => sb_format_expand('{sb_background}'), + set 'title' => '\V'.setc().'\:', + ]); +} +Irssi::settings_add_bool(setc, set 'prefer_name', 0); # +Irssi::settings_add_int( setc, set 'hide_empty', 0); # +Irssi::settings_add_int( setc, set 'hide_data', 0); # +Irssi::settings_add_str( setc, set 'detach', ''); # +Irssi::settings_add_int( setc, set 'detach_data', -3); # +Irssi::settings_add_bool(setc, set 'detach_aht', 0); # +Irssi::settings_add_int( setc, set 'hide_name_data', 0); # +Irssi::settings_add_int( setc, set 'maxlines', 9); # +Irssi::settings_add_int( setc, set 'maxcolumns', 4); # +Irssi::settings_add_int( setc, set 'block', 15); # +Irssi::settings_add_bool(setc, set 'sbar_maxlength', 1); # +Irssi::settings_add_int( setc, set 'height_adjust', 2); # +Irssi::settings_add_str( setc, set 'sort', 'refnum'); # +Irssi::settings_add_str( setc, set 'placement', 'bottom'); # +Irssi::settings_add_int( setc, set 'position', 0); # +Irssi::settings_add_bool(setc, set 'all_disable', 1); # +Irssi::settings_add_bool(setc, set 'viewer', 1); # +Irssi::settings_add_str( setc, set 'shared_sbar', 'OFF'); # +Irssi::settings_add_bool(setc, set 'mouse', 0); # +Irssi::settings_add_str( setc, set 'path', Irssi::get_irssi_dir . '/_windowlist'); # +Irssi::settings_add_str( setc, set 'custom_xform', ''); # +Irssi::settings_add_time(setc, set 'last_line_shade', '0'); # +Irssi::settings_add_int( setc, set 'mouse_offset', 1); # +Irssi::settings_add_int( setc, 'mouse_scroll', 3); # +Irssi::settings_add_int( setc, 'mouse_escape', 1); # +Irssi::settings_add_str( setc, 'banned_channels', ''); +Irssi::settings_add_bool(setc, 'banned_channels_on', 1); +Irssi::settings_add_str( setc, 'fancy_abbrev', 'fancy'); # +Irssi::settings_add_bool(setc, set 'no_mode_hint', 0); # +Irssi::settings_add_bool(setc, set 'viewer_launch', 1); # +Irssi::settings_add_str( setc, set 'viewer_launch_env', ''); # +Irssi::settings_add_str( setc, set 'viewer_tmux_position', 'left'); # +Irssi::settings_add_str( setc, set 'viewer_xwin_command', 'xterm +sb -e %A'); # +Irssi::settings_add_str( setc, set 'viewer_custom_command', ''); # + +Irssi::signal_add_last({ + 'setup changed' => 'setup_changed', + 'print text' => 'addPrintTextHook', + 'terminal resized' => 'termsize_changed', + 'setup reread' => 'screenFullRedraw', + 'window hilight' => 'wl_changed', + 'command format' => 'wl_changed', +}); +Irssi::signal_add({ + 'window changed' => 'window_changed', + 'window item changed' => 'wl_changed', + 'window changed automatic' => 'window_changed', + 'window created' => 'awins_changed', + 'window destroyed' => 'window_destroyed', + 'window name changed' => 'wl_changed', + 'window refnum changed' => 'refnum_changed', +}); +Irssi::signal_add_last('gui mouse' => 'mouse_escape'); +Irssi::signal_add_last('gui mouse' => 'mouse_scroll_event'); +Irssi::signal_add_last('gui mouse' => 'awl_mouse_event'); +Irssi::command_bind( setc() => runsub(setc()) ); +Irssi::command_bind( setc() . ' redraw' => 'screenFullRedraw' ); +Irssi::command_bind( setc() . ' restart' => 'restartViewerServer' ); +Irssi::command_bind( setc() . ' attach' => 'unhide_window' ); +Irssi::command_bind( setc() . ' detach' => 'hide_window' ); +Irssi::command_bind( setc() . ' ack' => 'ack_window' ); + +{ + my $l = set 'shared'; + { + no strict 'refs'; + *{$l} = $awl_shared_empty; + } + Irssi::statusbar_item_register($l, '$0', $l); +} + +awl_init(); + +# Mouse script based on irssi mouse patch by mirage +{ my $mouse_status = -1; # -1:off 0,1,2:filling mouse_combo + my @mouse_combo; # 0:button 1:x 2:y + my @mouse_previous; # previous contents of mouse_combo + + sub mouse_xterm_off { + $mouse_status = -1; + } + sub mouse_xterm { + $mouse_status = 0; + Irssi::timeout_add_once(10, 'mouse_xterm_off', undef); + } + + sub mouse_key_hook { + my ($key) = @_; + if ($mouse_status != -1) { + if ($mouse_status == 0) { + @mouse_previous = @mouse_combo; + #if @mouse_combo && $mouse_combo[0] < 64; + } + $mouse_combo[$mouse_status] = $key - 32; + $mouse_status++; + if ($mouse_status == 3) { + $mouse_status = -1; + # match screen coordinates + $mouse_combo[1]--; + $mouse_combo[2]--; + Irssi::signal_emit('gui mouse', @mouse_combo[0 .. 2], @mouse_previous[0 .. 2]); + } + Irssi::signal_stop; + } + } +} + +sub string_LCSS { + my $str = join "\0", @_; + (sort { length $b <=> length $a } $str =~ /(?=(.+).*\0.*\1)/g)[0] +} + +# workaround for issue #271 +{ package Irssi::Nick } + +# workaround for issue #572 +@Irssi::UI::Exec::ISA = 'Irssi::Windowitem' + if Irssi::version >= 20140822 && Irssi::version <= 20161101 && !@Irssi::UI::Exec::ISA; + +UNITCHECK +{ package AwlViewer; + use strict; + use warnings; + no warnings 'redefine'; + use Encode; + use IO::Socket::UNIX; + use IO::Select; + use List::Util qw(max); + use constant BLOCK_SIZE => 1024; + use constant RECONNECT_TIME => 5; + + my $sockpath; + + our $VERSION = '0.8'; + + our ($got_int, $resized, $timeout); + + my %vars; + my (%c2w, @seqlist); + my %mouse_coords; + my (@mouse, @last_mouse); + my ($err, $sock, $loop); + my ($keybuf, $rcvbuf); + my @screen; + my ($screenHeight, $screenWidth); + my ($disp_update, $fs_open, $one_shot_integration, $one_shot_resize); + my $integration_position; + my $show_title_bar; + + sub connect_it { + $sock = IO::Socket::UNIX->new( + Type => SOCK_STREAM, + Peer => $sockpath, + ); + unless ($sock) { + $err = $!; + return; + } + $sock->blocking(0); + $loop->add($sock); + } + + sub remove_conn { + my $fh = shift; + $loop->remove($fh); + $fh->close; + $sock = undef; + %vars = (); + @screen = (); + } + + { package Terminfo; # xterm + sub civis { "\e[?25l" } + sub sc { "\e7" } + sub cup { "\e[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' } + sub el { "\e[K" } + sub rc { "\e8" } + sub cnorm { "\e[?25h" } + sub setab { "\e[4" . $_[0] . 'm' } + sub setaf { "\e[3" . $_[0] . 'm' } + sub setaf16 { "\e[9" . $_[0] . 'm' } + sub setab16 { "\e[10" . $_[0] . 'm' } + sub setaf256 { "\e[38;5;" . $_[0] . 'm' } + sub setab256 { "\e[48;5;" . $_[0] . 'm' } + sub sgr0 { "\e[0m" } + sub bold { "\e[1m" } + sub it { "\e[3m" } + sub ul { "\e[4m" } + sub blink { "\e[5m" } + sub rev { "\e[7m" } + sub op { "\e[39;49m" } + sub exit_bold { "\e[22m" } + sub exit_it { "\e[23m" } + sub exit_ul { "\e[24m" } + sub exit_blink { "\e[25m" } + sub exit_rev { "\e[27m" } + sub smcup { "\e[?1049h" } + sub rmcup { "\e[?1049l" } + sub smmouse { "\e[?1000h\e[?1005h" } + sub rmmouse { "\e[?1005l\e[?1000l" } + } + + sub init { + $sockpath = shift // "$ENV{HOME}/.irssi/_windowlist"; + STDOUT->autoflush(1); + printf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}; + + `stty -icanon -echo`; + + $loop = IO::Select->new; + STDIN->blocking(0); + $loop->add(\*STDIN); + + $SIG{INT} = sub { + $got_int = 1 + }; + $SIG{WINCH} = sub { + $resized = 1 + }; + + $resized = 3; + + $disp_update = 2; + + $show_title_bar = 1; + } + + sub enter_fs { + return if $fs_open; + safe_print(Terminfo::rc, Terminfo::smcup, Terminfo::civis, Terminfo::smmouse); + $fs_open = 1; + } + + sub leave_fs { + return unless $fs_open; + safe_print(Terminfo::rmmouse, Terminfo::cnorm, Terminfo::rmcup); + safe_print(sprintf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}) if $_[0]; + + $fs_open = 0; + } + + sub end_prog { + leave_fs(); + STDIN->blocking(1); + `stty sane`; + printf "\r%s%sthanks for using %s\n", Terminfo::rc, Terminfo::el, $::IRSSI{name}; + } + + sub safe_print { + my $st = STDIN->blocking(1); + print @_; + STDIN->blocking($st); + } + + sub safe_qx { + my $st = STDIN->blocking(1); + my $ret = `$_[0]`; + STDIN->blocking($st); + $ret + } + + sub safe_print_sock { + return unless $sock; + my $was = $sock->blocking(1); + $sock->print(@_); + $sock->blocking($was); + } + + sub process_recv { + my $need = 0; + while ($rcvbuf =~ s/\n(.+)_BEGIN\n((?: .*\n)*)\1_END\n//) { + my $var = lc $1; + my $data = $2; + my @data = split "\n ", "\n$data ", -1; + shift @data; pop @data; + my $itembg = $vars{itembg}; + if ($var =~ s/list$//) { + $vars{$var} = \@data; + } + elsif ($var =~ s/map$//) { + $vars{$var} = +{ @data }; + } + else { + $vars{$var} = join "\n", @data; + } + $need = 1 if $var eq 'win'; + $need = 1 if $var eq 'redraw' && $vars{$var}; + if (($itembg//'') ne ($vars{itembg}//'')) { + $need = $vars{redraw} = 1; + } + _build_keymap() if $var eq 'key2'; + } + $need + } + + { my %ansi_table; + my ($i, $j, $k) = (0, 0, 0); + my %term_state; + sub reset_term_state { my %old_term = %term_state; %term_state = (); %old_term } + sub set_term_state { my %old_term = %term_state; %term_state = @_; %old_term } + %ansi_table = ( + # fe-common::core::formats.c:format_expand_styles + (map { my $t = $i++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setab16 : \&Terminfo::setab; + $n->($t) }) } (split //, '01234567' )), + (map { my $t = $j++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf16 : \&Terminfo::setaf; + $n->($t) }) } (split //, 'krgybmcw' )), + (map { my $t = $k++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf : \&Terminfo::setaf16; + $n->($t) }) } (split //, 'KRGYBMCW')), + # reset + n => sub { $term_state{hicolor} = 0; my $r = Terminfo::op; + for (qw(blink rev bold)) { + $r .= Terminfo->can("exit_$_")->() if delete $term_state{$_}; + } + { + local $ansi_table{n} = $ansi_table{N}; + $r .= formats_to_ansi_basic($vars{itembg}); + } + $r + }, + N => sub { reset_term_state(); Terminfo::sgr0 }, + # flash/bright + F => sub { my $n = 'blink'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # reverse + 8 => sub { my $n = 'rev'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # bold + "_" => sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # underline + U => sub { my $n = 'ul'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # italic + I => sub { my $n = 'it'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # bold, used as colour modifier if AWL_HI9 is set + 9 => $ENV{AWL_HI9} ? sub { $term_state{hicolor} ^= 1; '' } + : sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # delete other stuff + (map { $_ => sub { '' } } (split //, ':|>#[')), + # escape + (map { my $close = $_; $_ => sub { $close } } (split //, '{}%')), + ); + for my $base (0 .. 15) { + my $close = $base; + my $idx = ($close&8) | ($close&4)>>2 | ($close&2) | ($close&1)<<2; + $ansi_table{ (sprintf "x0%x", $close) } = + $ansi_table{ (sprintf "x0%X", $close) } = + sub { Terminfo::setab256($idx) }; + $ansi_table{ (sprintf "X0%x", $close) } = + $ansi_table{ (sprintf "X0%X", $close) } = + sub { Terminfo::setaf256($idx) }; + } + for my $plane (1 .. 6) { + for my $coord (0 .. 35) { + my $close = 16 + ($plane-1) * 36 + $coord; + my $ch = $coord < 10 ? $coord : chr( $coord - 10 + ord 'a' ); + $ansi_table{ "x$plane$ch" } = + $ansi_table{ "x$plane\U$ch" } = + sub { Terminfo::setab256($close) }; + $ansi_table{ "X$plane$ch" } = + $ansi_table{ "X$plane\U$ch" } = + sub { Terminfo::setaf256($close) }; + } + } + for my $gray (0 .. 23) { + my $close = 232 + $gray; + my $ch = chr( $gray + ord 'a' ); + $ansi_table{ "x7$ch" } = + $ansi_table{ "x7\U$ch" } = + sub { Terminfo::setab256($close) }; + $ansi_table{ "X7$ch" } = + $ansi_table{ "X7\U$ch" } = + sub { Terminfo::setaf256($close) }; + } + sub formats_to_ansi_basic { + my $o = shift; + $o =~ s/(%(X..|x..|.))/exists $ansi_table{$2} ? $ansi_table{$2}->() : $1/gex; + $o + } + } + + sub _header { + my $str = $vars{title} // uc ::setc(); + my $ccs = qr/%(?:X(?:[1-6][0-9A-Z]|7[A-X])|[0-9BCFGIKMNRUWY_])/i; + (my $stripstr = $str) =~ s/($ccs)//g; + my $space = int( ((abs $vars{block}) - length $stripstr) / (1 + length $stripstr)); + if ($space > 0) { + my $ss = ' ' x $space; + my @x = $str =~ /((?:$ccs)*\X(?:(?:$ccs)*$)?)/g; + $str = join $ss, '', @x, ''; + } + ($stripstr = $str) =~ s/($ccs)//g; + my $pad = max 0, (abs $vars{block}) - length $stripstr; + $str = ' ' x ($pad/2) . $str . ' ' x ($pad/2 + $pad%2); + $str + } + + sub _add_item { + my ($i, $j, $c, $wi, $screen, $mouse) = @_; + $screen->[$i][$j] = "%N%n$wi"; + if (exists $vars{mouse}{$c - 1}) { + $mouse->[$i][$j] = $vars{mouse}{$c - 1}; + } + } + sub update_screen { + $disp_update = 0; + unless ($sock && exists $vars{seplen} && exists $vars{block}) { + leave_fs(1); + return; + } + enter_fs(); + @screen = () if delete $vars{redraw}; + %mouse_coords = (); + my $ncols = ($vars{seplen} + abs $vars{block}) ? + int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0; + my $xenl = ($vars{seplen} + abs $vars{block}) + && $ncols > int( ($screenWidth + $vars{seplen} - 1) / ($vars{seplen} + abs $vars{block}) ); + my $nrows = $screenHeight - $vars{ha}; + my @wi = @{$vars{win}//[]}; + my $max_items = $ncols * $nrows; + my $c = $show_title_bar ? 1 : 0; + my $items = @wi + $c; + my $titems = $items > $max_items ? $max_items : $items; + my $i = 0; + my $j = 0; + my @new_screen; + my @new_mouse; + $new_screen[0][0] = _header() #. ' ' x $vars{seplen} + if $show_title_bar; + unless ($nrows > $ncols) { # line layout + ++$j if $show_title_bar; + for my $wi (@wi) { + if ($j >= $ncols) { + $j = 0; + ++$i; + } + last if $i >= $nrows; + _add_item($i, $j, $show_title_bar ? $c : $c + 1, + $wi, \@new_screen, \@new_mouse); + if ($c + 1 < $titems && $j + 1 < $ncols) { + $new_screen[$i][$j] .= $vars{separator}; + } + ++$j; + ++$c; + } + } + else { # column layout + ++$i if $show_title_bar; + for my $wi (@wi) { + if ($i >= $nrows) { + $i = 0; + ++$j; + } + last if $j >= $ncols; + _add_item($i, $j, $show_title_bar ? $c : $c + 1, + $wi, \@new_screen, \@new_mouse); + if ($c + $nrows < $titems) { + $new_screen[$i][$j] .= $vars{separator}; + } + ++$i; + ++$c; + } + } + my $step = $vars{seplen} + abs $vars{block}; + $i = 0; + my $str = Terminfo::sc . Terminfo::sgr0; + for (my $i = 0; $i < @new_screen; ++$i) { + for (my $j = 0; $j < @{$new_screen[$i]}; ++$j) { + if (defined $new_mouse[$i] && defined $new_mouse[$i][$j]) { + my $from = $j * $step; + $mouse_coords{$i}{$_} = $new_mouse[$i][$j] + for $from .. $from + abs $vars{block}; + } + next if defined $screen[$i] && defined $screen[$i][$j] + && $screen[$i][$j] eq $new_screen[$i][$j]; + $str .= Terminfo::cup($i, $j * $step) + . formats_to_ansi_basic($new_screen[$i][$j]) + . Terminfo::sgr0; + $str .= Terminfo::el if $j == $#{$new_screen[$i]} && (!$xenl || $j + 1 != $ncols); + } + } + for (@new_screen .. $screenHeight - 1) { + if (!@screen || defined $screen[$_]) { + $str .= Terminfo::cup($_, 0) . Terminfo::sgr0 . Terminfo::el; + } + } + $str .= Terminfo::rc; + safe_print $str; + @screen = @new_screen; + } + + sub handle_resize { + if (defined (my $r = safe_qx('stty size'))) { + ($screenHeight, $screenWidth) = split ' ', $r; + $resized = 0; + @screen = (); + $disp_update = 1; + if ($one_shot_integration == 2) { + $one_shot_resize--; + } + } + else { + } + } + + sub _build_keymap { + %c2w = reverse( %{$vars{key}}, %{$vars{key2}} ); + if (!grep { /^[+-]./ } keys %c2w) { + %c2w = (%c2w, map { ("-$_" => $c2w{$_}) } grep { !/^\^./ } keys %c2w); + } + %c2w = map { + my $key = $_; + s{^(-)?(\+)?(\^)?(.)}{ + join '', ( + ($1 ? "\e" : ''), + ($2 ? "\e\e" : ''), + ($3 ? "$4"^"@" : $4) + ) + }e; + $_ => $c2w{$key} + } keys %c2w; + @seqlist = sort { length $b <=> length $a } keys %c2w; + } + + sub _match_tmux { + length $ENV{TMUX} && exists $vars{irssienv}{tmux_srv} && length $vars{irssienv}{tmux_pane} + && $ENV{TMUX} eq $vars{irssienv}{tmux_srv} + } + + sub process_keys { + Encode::_utf8_on($keybuf); + my $win; + my $use_mouse; + my $maybe; + KEY: while (length $keybuf && !$maybe) { + $maybe = 0; + if ($keybuf =~ s/^\e\[M(.)(.)(.)//) { + @last_mouse = @mouse;# if @mouse && $mouse[0] < 64; + @mouse = map { -32 + ord } ($1, $2, $3); + $use_mouse = 1; + next KEY; + } + for my $s (@seqlist) { + if ($keybuf =~ s/^\Q$s//) { + $win = $c2w{$s}; + $use_mouse = 0; + next KEY; + } + elsif (length $keybuf < length $s && $s =~ /^\Q$keybuf/) { + $maybe = 1; + } + } + unless ($maybe) { + substr $keybuf, 0, 1, ''; + } + } + if ($use_mouse && @mouse && @last_mouse && + $mouse[2] == $last_mouse[2] && + $mouse[1] == $last_mouse[1] && + ($mouse[0] == 3 || $mouse[0] == 64 || $mouse[0] == 65)) { + if ($mouse[0] == 64) { + $win = 'up'; + } + elsif ($mouse[0] == 65) { + $win = 'down'; + } + elsif (exists $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}) { + $win = $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}; + } + elsif ($mouse[2] == 1 && $mouse[1] <= abs $vars{block}) { + $win = $last_mouse[0] != 0 ? 'last' : 'active'; + } + else { + } + } + if (defined $win) { + $win =~ s/^_//; + safe_print_sock("$win\n"); + if (!exists $ENV{AWL_AUTOFOCUS} || $ENV{AWL_AUTOFOCUS}) { + if (_match_tmux()) { + safe_qx("tmux selectp -t $vars{irssienv}{tmux_pane} 2>&1"); + } + elsif (exists $vars{irssienv}{xwinid}) { + safe_qx("wmctrl -ia $vars{irssienv}{xwinid} 2>/dev/null"); + } + } + } + Encode::_utf8_off($keybuf); + } + + sub check_integration { + return unless $vars{irssienv}; + return unless $sock && exists $vars{seplen} && exists $vars{block}; + if ($one_shot_integration == 1) { + my $nrows = $screenHeight - $vars{ha}; + my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0; + my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]}; + my $dcols_required = $nrows ? int($items/$nrows) + !!($items%$nrows) : 0; + my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0; + $rows_required = abs $vars{ml} + if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml})); + $dcols_required = abs $vars{mc} + if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc})); + my $rows = $rows_required + $vars{ha}; + my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen}; + if (_match_tmux()) { + # int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ); + my ($pos_flag, $before); + if ($integration_position eq 'left') { + $pos_flag = 'h'; + $before = 1; + } + elsif ($integration_position eq 'top') { + $pos_flag = 'v'; + $before = 1; + } + elsif ($integration_position eq 'right') { + $pos_flag = 'h'; + } + else { + $pos_flag = 'v'; + } + my @cmd = "joinp -d$pos_flag -s $ENV{TMUX_PANE} -t $vars{irssienv}{tmux_pane}"; + push @cmd, "swapp -d -t $ENV{TMUX_PANE} -s $vars{irssienv}{tmux_pane}" + if $before; + $cols = max($cols, 2); + $rows = max($rows, 2); + + safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1"); + } + else { + $resized = 1; + #safe_qx("resize -s $screenHeight $cols 2>&1") + # if $cols > 0; + } + $one_shot_integration++; + if ($resized == 1) { + handle_resize(); + resize_integration(); + } + } + elsif ($one_shot_integration == 2) { + resize_integration(1); + } + } + + sub resize_integration { + return unless $one_shot_integration; + return unless ($one_shot_resize//0) < 0 || shift; + return if ($one_shot_resize//0) > 0; + + my $nrows = $screenHeight - $vars{ha}; + my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0; + my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]}; + my $dcols_required = $nrows ? (int($items/$nrows) + !!($items%$nrows)) : 0; + my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0; + $rows_required = abs $vars{ml} + if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml})); + $dcols_required = abs $vars{mc} + if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc})); + my $rows = $rows_required + $vars{ha}; + my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen}; + if (_match_tmux()) { + my $pos_flag; + my $before = 0; + if ($integration_position eq 'left') { + $pos_flag = 'h'; + $before = 1; + } + elsif ($integration_position eq 'top') { + $pos_flag = 'v'; + $before = 1; + } + elsif ($integration_position eq 'right') { + $pos_flag = 'h'; + } + else { + $pos_flag = 'v'; + } + my @cmd; + # hard tmux limits + $cols = max($cols, 2); + $rows = max($rows, 2); + if ($pos_flag eq 'h' && $cols != $screenWidth) { + my $change = $screenWidth - $cols; + my $dir = ($before ^ ($change<0)) ? 'L' : 'R'; + push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}"; + #push @cmd, "resizep -x $cols -t $ENV{TMUX_PANE}"; + $one_shot_resize = 1; + } + if ($pos_flag eq 'v' && $rows != $screenHeight) { + #push @cmd, "resizep -y $rows -t $ENV{TMUX_PANE}"; + my $change = $screenHeight - $rows; + my $dir = ($before ^ ($change<0)) ? 'U' : 'D'; + push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}"; + $one_shot_resize = 1; + } + + safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1") + if @cmd; + } + else { + $cols = max($cols, 1); + $rows = max($rows, 1); + unless ($nrows > $ncols) { # line layout + if ($rows != $screenHeight) { + safe_qx("resize -s $rows $screenWidth 2>&1"); + $one_shot_resize = 1; + } + } + else { + if ($cols != $screenWidth) { + safe_qx("resize -s $screenHeight $cols 2>&1"); + $one_shot_resize = 1; + } + } + } + if ($resized == 1) { + handle_resize(); + } + } + + sub init_integration { + return unless $one_shot_integration; + if (_match_tmux()) { + } + else { + } + safe_print("\e]2;".(uc ::setc())."\e\\"); + } + + sub main { + require Getopt::Std; + my %opts; + Getopt::Std::getopts('1p:', \%opts); + my $one_shot = $opts{1}; + $integration_position = $opts{p}; + $one_shot_integration = 0+!!$one_shot; + #shift if @_ && $_[0] eq '--'; + &init; + $show_title_bar = 0 if $ENV{AWL_NOTITLE}; + init_integration(); + until ($got_int) { + $timeout = undef; + if ($resized) { + if ($resized == 1) { + $timeout = 1; + $resized++; + } + else { + handle_resize(); + resize_integration(); + } + } + unless ($sock || $timeout) { + connect_it(); + } + $timeout ||= RECONNECT_TIME unless $sock; + update_screen() if $disp_update; + SELECT: while (my @read = $loop->can_read($timeout)) { + for my $fh (@read) { + if ($fh == \*STDIN) { + if (read STDIN, my $buf, BLOCK_SIZE) { + do { + $keybuf .= $buf; + } while read STDIN, $buf, BLOCK_SIZE; + } + else { + $got_int = 1; + last SELECT; + } + } + else { + if ($fh->read(my $buf, BLOCK_SIZE)) { + do { + $rcvbuf .= $buf; + } while $fh->read($buf, BLOCK_SIZE); + } + else { + $disp_update = 1; + remove_conn($fh); + if ($one_shot) { + $got_int = 1; + last SELECT; + } + $timeout ||= RECONNECT_TIME; + } + } + } + $disp_update |= process_recv() if length $rcvbuf; + process_keys() if length $keybuf; + check_integration() if $one_shot; + update_screen() if $disp_update; + } + continue { + } + } + end_prog(); + } +} + +1; + +# Changelog +# ========= +# 1.8 +# - use string_width in Irssi 1.2.0 +# +# 1.7 +# - fix crash on invalid /set awl_sort, introduced in 1.6, reported by +# tpetazzoni +# - delay viewer initialisation +# - improve race condition on tmux resize integration +# +# 1.6 +# - add detach setting to hide windows +# - fix race condition when loading the script, reported by madduck +# - improve compatibility with irssi 1.2 +# - add special value lru to awl_sort to sort windows by usage +# +# 1.5 +# - improve compat. with sideways splits +# +# 1.4 +# - fix line wrapping in some themes, reported by justanotherbody +# - fix named window key detection, reported by madduck +# - make title (in viewer and shared_sbar) configurable +# +# 1.3 +# - workaround for irssi issue #572 +# +# 1.2 +# - new format to choose abbreviation character +# +# 1.1 +# - infinite loop on shortening certain window names reported by Kalan +# +# 1.0 +# - new awl_viewer_launch setting and an array of related settings +# - fixed regression bug /exec -interactive +# - fixed some warnings in perl 5.10 reported by kl3 +# - workaround for crash due to infinite recursion in irssi's Perl +# error handling +# +# 0.9 +# - fix endless loop in awin detection code! +# - correct colour swap in awl_viewer +# - fix passing of alternate socket path to the viewer +# - potential undefinedness in mouse refnum hinted at by Canopus +# - fixed regression bug /exec -interactive +# - add case-insensitive modifier to awl_sort +# - run custom_xform on awl_prefer_name also +# - avoid inconsistent active window state after awin detection +# reported by ss +# - revert %9-hack in the viewer prompted by discussion with pierrot +# - fix new warning in perl 5.22 +# +# 0.8 +# - replace fifo mode with external viewer script +# - remove bundled cpan modules +# - work around bogus irssi warning +# - improve mouse support +# - workaround for broken cumode in irssi 0.8.15 +# - fix handling of non-meta windows (uninitialized warning) +# - add 256 colour support, strip true colour codes +# - fix totally bogus $N padding reported by Ed S. +# - make /window goto #name mappings work but ignore non-existant ones +# - improve incomplete reads reported by bcode +# - fix single % in awl_viewer reported by bcode +# - add support for key bindings by nike and ferret +# - coerce utf8 key binds +# - add settings: custom_xform, last_line_shade, hide_name_data +# - abbreviations were broken in some cases +# - fix some misuse of / as cmdchar in mouse script reported by bcode +# - add shared status bar mode +# - ${type} variables for custom_xform setting +# - crash if custom_xform had runtime error +# - update sorting documentation +# - fix odd case in size calculation noted by lasers +# - add missing font styles to the viewer reported by ishanyx +# - add italic +# +# 0.7g +# - remove screen support and replace it with fifo support +# - add double-width support to the shortener +# - correct documentation regarding $T vs. display_header +# - add missing refresh for window item changed (thanks vague) +# - add visible windows +# - add exemptions for active window +# - workaround for hiding the window changes from trackbar +# - hack to force 16colours in screen mode +# - remember last window (reported by earthnative) +# - wrong window focus on new queries (reported by emsid) +# - dataloss bug on trying to remember last window +# +# 0.6d+ +# - add support for network headers +# - fixed regression bug /exec -interactive +# +# 0.6ca+ +# - add screen support (from nicklist.pl) +# - names can now have a max length and window names can be used +# - fixed a bug with block display in screen mode and status bar mode +# - added space handling to ir_fe and removed it again +# - now handling formats on my own +# - started to work on $tag display +# - added warning about missing sb_act_none abstract leading to +# - display*active settings +# - added warning about the bug in awl_display_(no)key_active settings +# - mouse hack +# +# 0.5d +# - add setting to also hide the last status bar if empty (awl_all_disable) +# - reverted to old utf8 code to also calculate broken utf8 length correctly +# - simplified dealing with status bars in wlreset +# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9 +# - fixed bug in handling channel #$$ +# - reset background colour at the beginning of an entry +# +# 0.4d +# - fixed order of disabling status bars +# - several attempts at special chars, without any real success +# and much more weird new bugs caused by this +# - setting to specify sort order +# - reduced timeout values +# - added awl_hide_data +# - make it so the dynamic sub is actually deleted +# - fix a bug with removing of the last separator +# - take into consideration parse_special +# +# 0.3b +# - automatically kill old status bars +# - reset on /reload +# - position/placement settings +# +# 0.2 +# - automated retrieval of key bindings (thanks grep.pl authors) +# - improved removing of status bars +# - got rid of status chop +# +# 0.1 +# - Based on chanact.pl which was apparently based on lightbar.c and +# nicklist.pl with various other ideas from random scripts. diff --git a/.irssi/scripts/autorun/adv_windowlist.pl b/.irssi/scripts/autorun/adv_windowlist.pl new file mode 120000 index 0000000..d210a5c --- /dev/null +++ b/.irssi/scripts/autorun/adv_windowlist.pl @@ -0,0 +1 @@ +../adv_windowlist.pl \ No newline at end of file diff --git a/.irssi/scripts/autorun/trackbar.pl b/.irssi/scripts/autorun/trackbar.pl new file mode 120000 index 0000000..8c5a483 --- /dev/null +++ b/.irssi/scripts/autorun/trackbar.pl @@ -0,0 +1 @@ +../trackbar.pl \ No newline at end of file diff --git a/.irssi/scripts/trackbar.pl b/.irssi/scripts/trackbar.pl new file mode 100644 index 0000000..0e71434 --- /dev/null +++ b/.irssi/scripts/trackbar.pl @@ -0,0 +1,605 @@ +## trackbar.pl +# +# This little script will do just one thing: it will draw a line each time you +# switch away from a window. This way, you always know just upto where you've +# been reading that window :) It also removes the previous drawn line, so you +# don't see double lines. +# +# redraw trackbar only works on irssi 0.8.17 or higher. +# +## + +## Usage: +# +# The script works right out of the box, but if you want you can change +# the working by /set'ing the following variables: +# +# Setting: trackbar_style +# Description: This setting will be the color of your trackbar line. +# By default the value will be '%K', only Irssi color +# formats are allowed. If you don't know the color formats +# by heart, you can take a look at the formats documentation. +# You will find the proper docs on http://www.irssi.org/docs. +# +# Setting: trackbar_string +# Description: This is the string that your line will display. This can +# be multiple characters or just one. For example: '~-~-' +# The default setting is '-'. +# Here are some unicode characters you can try: +# "───" => U+2500 => a line +# "═══" => U+2550 => a double line +# "━━━" => U+2501 => a wide line +# "▭ " => U+25ad => a white rectangle +# +# Setting: trackbar_use_status_window +# Description: If this setting is set to OFF, Irssi won't print a trackbar +# in the statuswindow +# +# Setting: trackbar_ignore_windows +# Description: A list of windows where no trackbar should be printed +# +# Setting: trackbar_print_timestamp +# Description: If this setting is set to ON, Irssi will print the formatted +# timestamp in front of the trackbar. +# +# Setting: trackbar_require_seen +# Description: Only clear the trackbar if it has been scrolled to. +# +# Setting: trackbar_all_manual +# Description: Never clear the trackbar until you do /mark. +# +# /mark is a command that will redraw the line at the bottom. +# +# Command: /trackbar, /trackbar goto +# Description: Jump to where the trackbar is, to pick up reading +# +# Command: /trackbar keep +# Description: Keep this window's trackbar where it is the next time +# you switch windows (then this flag is cleared again) +# +# Command: /mark, /trackbar mark +# Description: Remove the old trackbar and mark the bottom of this +# window with a new trackbar +# +# Command: /trackbar markvisible +# Description: Like mark for all visible windows +# +# Command: /trackbar markall +# Description: Like mark for all windows +# +# Command: /trackbar remove +# Description: Remove this window's trackbar +# +# Command: /trackbar removeall +# Description: Remove all windows' trackbars +# +# Command: /trackbar redraw +# Description: Force redraw of trackbars +# +## + +## +# +# For bugreports and other improvements contact one of the authors. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this script; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +## + +use strict; +use warnings; +use vars qw($VERSION %IRSSI); + +$VERSION = "2.8"; # 89f27cc2c12368e + +%IRSSI = ( + authors => "Peter 'kinlo' Leurs, Uwe Dudenhoeffer, " . + "Michiel Holtkamp, Nico R. Wohlgemuth, " . + "Geert Hauwaerts", + contact => 'peter@pfoe.be', + patchers => 'Johan Kiviniemi (UTF-8), Uwe Dudenhoeffer (on-upgrade-remove-line)', + name => 'trackbar', + description => 'Shows a bar where you have last read a window.', + license => 'GNU General Public License', + url => 'http://www.pfoe.be/~peter/trackbar/', + commands => 'trackbar', +); + +## Comments and remarks. +# +# This script uses settings. +# Use /SET to change the value or /TOGGLE to switch it on or off. +# +# +# Tip: The command 'trackbar' is very usefull if you bind that to a key, +# so you can easily jump to the trackbar. Please see 'help bind' for +# more information about keybindings in Irssi. +# +# Command: /BIND meta2-P key F1 +# /BIND F1 command trackbar +# +## + +## Bugfixes and new items in this rewrite. +# +# * Remove all the trackbars before upgrading. +# * New setting trackbar_use_status_window to control the statuswindow trackbar. +# * New setting trackbar_print_timestamp to print a timestamp or not. +# * New command 'trackbar' to scroll up to the trackbar. +# * When resizing your terminal, Irssi will update all the trackbars to the new size. +# * When changing trackbar settings, change all the trackbars to the new settings. +# * New command 'trackbar mark' to draw a new trackbar (The old '/mark'). +# * New command 'trackbar markall' to draw a new trackbar in each window. +# * New command 'trackbar remove' to remove the trackbar from the current window. +# * New command 'trackbar removeall' to remove all the trackbars. +# * Don't draw a trackbar in empty windows. +# * Added a version check to prevent Irssi redraw errors. +# * Fixed a bookmark NULL versus 0 bug. +# * Fixed a remove-line bug in Uwe Dudenhoeffer his patch. +# * New command 'help trackbar' to display the trackbar commands. +# * Fixed an Irssi startup bug, now processing each auto-created window. +# +## + +## Known bugs and the todolist. +# +# Todo: * Instead of drawing a line, invert the line. +# +## + +## Authors: +# +# - Main maintainer & author: Peter 'kinlo' Leurs +# - Many thanks to Timo 'cras' Sirainen for placing me on my way +# - on-upgrade-remove-line patch by Uwe Dudenhoeffer +# - trackbar resizing by Michiel Holtkamp (02 Jul 2012) +# - scroll to trackbar, window excludes, and timestamp options by Nico R. +# Wohlgemuth (22 Sep 2012) +# +## + +## Version history: +# +# 2.8: - fix /^join bug +# 2.7: - add /set trackbar_all_manual option +# 2.5: - merge back on scripts.irssi.org +# - fix /trackbar redraw broken in 2.4 +# - fix legacy encodings +# - add workaround for irssi issue #271 +# 2.4: - add support for horizontal splits +# 2.3: - add some features for seen tracking using other scripts +# 2.0: - big rewrite based on 1.4 +# * removed /tb, you can have it with /alias tb trackbar if you want +# * subcommand and settings changes: +# /trackbar vmark => /trackbar markvisible +# /trackbar scroll => /trackbar goto (or just /trackbar) +# /trackbar help => /help trackbar +# /set trackbar_hide_windows => /set trackbar_ignore_windows +# /set trackbar_timestamp => /set trackbar_print_timestamp +# * magic line strings were removed, just paste the unicode you want! +# * trackbar_timestamp_styled is not currently supported +# 1.9: - add version guard +# 1.8: - sub draw_bar +# 1.7: - Added /tb scroll, trackbar_hide_windows, trackbar_timestamp_timestamp +# and trackbar_timestamp_styled +# 1.6: - Work around Irssi resize bug, please do /upgrade! (see below) +# 1.5: - Resize trackbars in all windows when terminal is resized +# 1.4: - Changed our's by my's so the irssi script header is valid +# - Removed utf-8 support. In theory, the script should work w/o any +# problems for utf-8, just set trackbar_string to a valid utf-8 character +# and everything *should* work. However, this script is being plagued by +# irssi internal bugs. The function Irssi::settings_get_str does NOT handle +# unicode strings properly, hence you will notice problems when setting the bar +# to a unicode char. For changing your bar to utf-8 symbols, read the line sub. +# 1.3: - Upgrade now removes the trackbars. +# - Some code cleanups, other defaults +# - /mark sets the line to the bottom +# 1.2: - Support for utf-8 +# - How the bar looks can now be configured with trackbar_string +# and trackbar_style +# 1.1: - Fixed bug when closing window +# 1.0: - Initial release +# +## + +use Irssi; +use Irssi::TextUI; +use Encode; + +use POSIX qw(strftime); + +sub cmd_help { + my ($args) = @_; + if ($args =~ /^trackbar *$/i) { + print CLIENTCRAP <<HELP +%9Syntax:%9 + +TRACKBAR +TRACKBAR GOTO +TRACKBAR KEEP +TRACKBAR MARK +TRACKBAR MARKVISIBLE +TRACKBAR MARKALL +TRACKBAR REMOVE +TRACKBAR REMOVEALL +TRACKBAR REDRAW + +%9Parameters:%9 + + GOTO: Jump to where the trackbar is, to pick up reading + KEEP: Keep this window's trackbar where it is the next time + you switch windows (then this flag is cleared again) + MARK: Remove the old trackbar and mark the bottom of this + window with a new trackbar + MARKVISIBLE: Like mark for all visible windows + MARKALL: Like mark for all windows + REMOVE: Remove this window's trackbar + REMOVEALL: Remove all windows' trackbars + REDRAW: Force redraw of trackbars + +%9Description:%9 + + Manage a trackbar. Without arguments, it will scroll up to the trackbar. + +%9Examples:%9 + + /TRACKBAR MARK + /TRACKBAR REMOVE +HELP + } +} + +Irssi::theme_register([ + 'trackbar_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.', + 'trackbar_wrong_version', '%R>>%n %_Trackbar:%_ Please upgrade your client to 0.8.17 or above if you would like to use this feature of trackbar.', + 'trackbar_all_removed', '%R>>%n %_Trackbar:%_ All the trackbars have been removed.', + 'trackbar_not_found', '%R>>%n %_Trackbar:%_ No trackbar found in this window.', +]); + +my $old_irssi = Irssi::version < 20140701; +sub check_version { + if ($old_irssi) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_wrong_version'); + return; + } else { + return 1; + } +} + +sub is_utf8 { + lc Irssi::settings_get_str('term_charset') eq 'utf-8' +} + +my (%config, %keep_trackbar, %unseen_trackbar); + +sub remove_one_trackbar { + my $win = shift; + my $view = shift || $win->view; + my $line = $view->get_bookmark('trackbar'); + if (defined $line) { + my $bottom = $view->{bottom}; + $view->remove_line($line); + $win->command('^scrollback end') if $bottom && !$win->view->{bottom}; + $view->redraw; + } +} + +sub add_one_trackbar_pt1 { + my $win = shift; + my $view = shift || $win->view; + + my $last_cur_line = $view->{buffer}{cur_line}{_irssi}; + $win->print(line($win->{width}), MSGLEVEL_NEVER); + + my $cur_line = $win->view->{buffer}{cur_line}{_irssi}; # get a fresh buffer + + $last_cur_line ne $cur_line # printing was successful +} + +sub add_one_trackbar_pt2 { + my $win = shift; + my $view = $win->view; + + $view->set_bookmark_bottom('trackbar'); + $unseen_trackbar{ $win->{_irssi} } = 1; + Irssi::signal_emit("window trackbar added", $win); + $view->redraw; +} + +sub update_one_trackbar { + my $win = shift; + my $view = shift || $win->view; + my $force = shift; + my $ignored = win_ignored($win, $view); + my $success; + + $success = add_one_trackbar_pt1($win, $view) ? 1 : 0 + if $force || !$ignored; + + remove_one_trackbar($win, $view) + if ( $success || !defined $success ) && ( $force || !defined $force || !$ignored ); + + add_one_trackbar_pt2($win) + if $success; +} + +sub win_ignored { + my $win = shift; + my $view = shift || $win->view; + return 1 unless $view->{buffer}{lines_count}; + return 1 if $win->{name} eq '(status)' && !$config{use_status_window}; + no warnings 'uninitialized'; + return 1 if grep { $win->{name} eq $_ || $win->{refnum} eq $_ + || $win->get_active_name eq $_ } @{ $config{ignore_windows} }; + return 0; +} + +sub sig_window_changed { + my ($newwindow, $oldwindow) = @_; + return unless $oldwindow; + redraw_one_trackbar($newwindow) unless $old_irssi; + trackbar_update_seen($newwindow); + return if delete $keep_trackbar{ $oldwindow->{_irssi} }; + trackbar_update_seen($oldwindow); + return if $config{require_seen} && $unseen_trackbar{ $oldwindow->{_irssi } }; + return if $config{all_manual}; + update_one_trackbar($oldwindow, undef, 0); +} + +sub trackbar_update_seen { + my $win = shift; + return unless $win; + return unless $unseen_trackbar{ $win->{_irssi} }; + + my $view = $win->view; + my $line = $view->get_bookmark('trackbar'); + unless ($line) { + delete $unseen_trackbar{ $win->{_irssi} }; + Irssi::signal_emit("window trackbar seen", $win); + return; + } + my $startline = $view->{startline}; + return unless $startline; + + if ($startline->{info}{time} < $line->{info}{time} + || $startline->{_irssi} == $line->{_irssi}) { + delete $unseen_trackbar{ $win->{_irssi} }; + Irssi::signal_emit("window trackbar seen", $win); + } +} + +sub screen_length; +{ local $@; + eval { require Text::CharWidth; }; + unless ($@) { + *screen_length = sub { Text::CharWidth::mbswidth($_[0]) }; + } + else { + *screen_length = sub { + my $temp = shift; + Encode::_utf8_on($temp) if is_utf8(); + length($temp) + }; + } +} + +{ my %strip_table = ( + (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')), + (map { $_ => $_ } (split //, '{}%')), + ); + sub c_length { + my $o = Irssi::strip_codes($_[0]); + $o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} : + $2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex; + screen_length($o) + } +} + +sub line { + my ($width, $time) = @_; + my $string = $config{string}; + $string = ' ' unless length $string; + $time ||= time; + + Encode::_utf8_on($string) if is_utf8(); + my $length = c_length($string); + + my $format = ''; + if ($config{print_timestamp}) { + $format = $config{timestamp_str}; + $format =~ y/%/\01/; + $format =~ s/\01\01/%/g; + $format = strftime($format, localtime $time); + $format =~ y/\01/%/; + } + + my $times = $width / $length; + $times += 1 if $times != int $times; + my $style = "$config{style}"; + Encode::_utf8_on($style) if is_utf8(); + $format .= $style; + $width -= c_length($format); + $string x= $times; + chop $string while length $string && c_length($string) > $width; + return $format . $string; +} + +sub remove_all_trackbars { + for my $window (Irssi::windows) { + next unless ref $window; + remove_one_trackbar($window); + } +} + +sub UNLOAD { + remove_all_trackbars(); +} + +sub redraw_one_trackbar { + my $win = shift; + my $view = $win->view; + my $line = $view->get_bookmark('trackbar'); + return unless $line; + my $bottom = $view->{bottom}; + $win->print_after($line, MSGLEVEL_NEVER, line($win->{width}, $line->{info}{time}), + $line->{info}{time}); + $view->set_bookmark('trackbar', $win->last_line_insert); + $view->remove_line($line); + $win->command('^scrollback end') if $bottom && !$win->view->{bottom}; + $view->redraw; +} + +sub redraw_trackbars { + return unless check_version(); + for my $win (Irssi::windows) { + next unless ref $win; + redraw_one_trackbar($win); + } +} + +sub goto_trackbar { + my $win = Irssi::active_win; + my $line = $win->view->get_bookmark('trackbar'); + + if ($line) { + $win->command("scrollback goto ". strftime("%d %H:%M:%S", localtime($line->{info}{time}))); + } else { + $win->printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_not_found'); + } +} + +sub cmd_mark { + update_one_trackbar(Irssi::active_win, undef, 1); +} + +sub cmd_markall { + for my $window (Irssi::windows) { + next unless ref $window; + update_one_trackbar($window); + } +} + +sub signal_stop { + Irssi::signal_stop; +} + +sub cmd_markvisible { + my @wins = Irssi::windows; + my $awin = + my $bwin = Irssi::active_win; + my $awin_counter = 0; + Irssi::signal_add_priority('window changed' => 'signal_stop', -99); + do { + Irssi::active_win->command('window up'); + $awin = Irssi::active_win; + update_one_trackbar($awin); + ++$awin_counter; + } until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins); + Irssi::signal_remove('window changed' => 'signal_stop'); +} + +sub cmd_trackbar_remove_one { + remove_one_trackbar(Irssi::active_win); +} + +sub cmd_remove_all_trackbars { + remove_all_trackbars(); + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_all_removed'); +} + +sub cmd_keep_once { + $keep_trackbar{ Irssi::active_win->{_irssi} } = 1; +} + +sub trackbar_runsub { + my ($data, $server, $item) = @_; + $data =~ s/\s+$//g; + + if ($data) { + Irssi::command_runsub('trackbar', $data, $server, $item); + } else { + goto_trackbar(); + } +} + +sub update_config { + my $was_status_window = $config{use_status_window}; + $config{style} = Irssi::settings_get_str('trackbar_style'); + $config{string} = Irssi::settings_get_str('trackbar_string'); + $config{require_seen} = Irssi::settings_get_bool('trackbar_require_seen'); + $config{all_manual} = Irssi::settings_get_bool('trackbar_all_manual'); + $config{ignore_windows} = [ split /[,\s]+/, Irssi::settings_get_str('trackbar_ignore_windows') ]; + $config{use_status_window} = Irssi::settings_get_bool('trackbar_use_status_window'); + $config{print_timestamp} = Irssi::settings_get_bool('trackbar_print_timestamp'); + if (defined $was_status_window && $was_status_window != $config{use_status_window}) { + if (my $swin = Irssi::window_find_name('(status)')) { + if ($config{use_status_window}) { + update_one_trackbar($swin); + } + else { + remove_one_trackbar($swin); + } + } + } + if ($config{print_timestamp}) { + my $ts_format = Irssi::settings_get_str('timestamp_format'); + my $ts_theme = Irssi::current_theme->get_format('fe-common/core', 'timestamp'); + my $render_str = Irssi::current_theme->format_expand($ts_theme); + (my $ts_escaped = $ts_format) =~ s/([%\$])/$1$1/g; + $render_str =~ s/(?|\$(.)(?!\w)|\$\{(\w+)\})/$1 eq 'Z' ? $ts_escaped : $1/ge; + $config{timestamp_str} = $render_str; + } + redraw_trackbars() unless $old_irssi; +} + +Irssi::settings_add_str('trackbar', 'trackbar_string', is_utf8() ? "\x{2500}" : '-'); +Irssi::settings_add_str('trackbar', 'trackbar_style', '%K'); +Irssi::settings_add_str('trackbar', 'trackbar_ignore_windows', ''); +Irssi::settings_add_bool('trackbar', 'trackbar_use_status_window', 1); +Irssi::settings_add_bool('trackbar', 'trackbar_print_timestamp', 0); +Irssi::settings_add_bool('trackbar', 'trackbar_require_seen', 0); +Irssi::settings_add_bool('trackbar', 'trackbar_all_manual', 0); + +update_config(); + +Irssi::signal_add_last( 'mainwindow resized' => 'redraw_trackbars') + unless $old_irssi; + +Irssi::signal_register({'window trackbar added' => [qw/Irssi::UI::Window/]}); +Irssi::signal_register({'window trackbar seen' => [qw/Irssi::UI::Window/]}); +Irssi::signal_register({'gui page scrolled' => [qw/Irssi::UI::Window/]}); +Irssi::signal_add_last('gui page scrolled' => 'trackbar_update_seen'); + +Irssi::signal_add('setup changed' => 'update_config'); +Irssi::signal_add_priority('session save' => 'remove_all_trackbars', Irssi::SIGNAL_PRIORITY_HIGH-1); + +Irssi::signal_add('window changed' => 'sig_window_changed'); + +Irssi::command_bind('trackbar goto' => 'goto_trackbar'); +Irssi::command_bind('trackbar keep' => 'cmd_keep_once'); +Irssi::command_bind('trackbar mark' => 'cmd_mark'); +Irssi::command_bind('trackbar markvisible' => 'cmd_markvisible'); +Irssi::command_bind('trackbar markall' => 'cmd_markall'); +Irssi::command_bind('trackbar remove' => 'cmd_trackbar_remove_one'); +Irssi::command_bind('trackbar removeall' => 'cmd_remove_all_trackbars'); +Irssi::command_bind('trackbar redraw' => 'redraw_trackbars'); +Irssi::command_bind('trackbar' => 'trackbar_runsub'); +Irssi::command_bind('mark' => 'cmd_mark'); +Irssi::command_bind_last('help' => 'cmd_help'); + +Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_loaded', $IRSSI{name}, $VERSION, $IRSSI{authors}); + +# workaround for issue #271 +{ package Irssi::Nick } diff --git a/.profile b/.profile new file mode 100644 index 0000000..161796e --- /dev/null +++ b/.profile @@ -0,0 +1,42 @@ +if [ -d "${HOME}/bin" ]; then + pathprepend "${HOME}/bin" + pathprepend "${HOME}/bin/$(uname -n)" +fi + +if [ -d "${HOME}/AppImages" ]; then + pathappend "${HOME}/AppImages" +fi + +# Use the new build backend in docker +export DOCKER_BUILDKIT=1 + +# Parallel portage +export NUMCPUS=$(nproc) +export NUMCPUSplusone=$((NUMCPUS + 1)) +export MAKEOPTS="-j${NUMCPUSplusone} -l${NUMCPUS}" +export EMERGE_DEFAULT_OPTS="--jobs=${NUMCPUSplusone} --load-average=${NUMCPUS}" + +# Set prompt +GREEN="\[$(tput setaf 2)\]" +RESET="\[$(tput sgr0)\]" +export PS1="${GREEN}\w${RESET} $(porcelain) ❱ " +# enlable vi mode +set -o vi +bind -m vi-insert -x '"\C-l": clear' + +# Set less default options +export LESS='--hilite-search --hilite-unread --long-prompt --raw-control-chars --chop-long-lines' + +# Set the rsync ssh command to use by default +export RSYNC_RSH='ssh -T -x -o Compression=no' + +# Alias shell commands +# alias logout='dbus-send --session --type=method_call --print-reply --dest=' +alias vim=nvim +alias l='exa --long --classify --group --header --group-directories-first' +alias ..='cd ..' +alias ssh='TERM=xterm-256color ssh' +alias rsyncez='rsync --verbose --archive --human-readable --partial --progress --stats --protect-args --rsh="ssh -o Compression=no"' +alias webcam='mpv --profile=webcam av://v4l2:/dev/video0' +alias balssh='ssh -p 22222 -o User=root -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' +alias balscp='scp -P 22222 -o User=root -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' diff --git a/.vim/.ignore b/.vim/.ignore new file mode 100644 index 0000000..060f79b --- /dev/null +++ b/.vim/.ignore @@ -0,0 +1 @@ +plugged/ diff --git a/.vim/UltiSnips/all.snippets b/.vim/UltiSnips/all.snippets new file mode 100644 index 0000000..780be8b --- /dev/null +++ b/.vim/UltiSnips/all.snippets @@ -0,0 +1,9 @@ +snippet sign "Signature" +${1:Cheers}, + +Robert Günzler +endsnippet + +snippet now "Timestamp (rfc3999)" +`now` +endsnippet diff --git a/.vim/UltiSnips/ebuild.snippets b/.vim/UltiSnips/ebuild.snippets new file mode 100644 index 0000000..ef736ab --- /dev/null +++ b/.vim/UltiSnips/ebuild.snippets @@ -0,0 +1,25 @@ +snippet eapi7 "eapi7" +# Copyright 2019 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=7 + +DESCRIPTION="$2" +HOMEPAGE="$3" + +if [[ ${PV} = *9999* ]]; then + inherit git-r3 + EGIT_REPO_URI="https://github.com/$1/${PN}.git" +else + SRC_URI="https://github.com/$1/${PN}/releases/download/v${PV}/${P}.tar.gz -> ${P}.tar.gz" + KEYWORDS="~amd64 ~arm64 ~x86" +fi + +LICENSE="MIT" +SLOT="0" +IUSE="" + +DEPEND="$0" +RDEPEND="${DEPEND}" +BDEPEND="" +endsnippet diff --git a/.vim/UltiSnips/go.snippets b/.vim/UltiSnips/go.snippets new file mode 100644 index 0000000..9233be0 --- /dev/null +++ b/.vim/UltiSnips/go.snippets @@ -0,0 +1,12 @@ +snippet main "func main()" +func main() { + ${0} +} +endsnippet + +snippet 'if err' "if err != nil {}" +if err != nil { + $1 +} +$0 +endsnippet diff --git a/.vim/UltiSnips/markdown.snippets b/.vim/UltiSnips/markdown.snippets new file mode 100644 index 0000000..9972963 --- /dev/null +++ b/.vim/UltiSnips/markdown.snippets @@ -0,0 +1,20 @@ +snippet link "Link" +[$1](${2:${VISUAL}}) +endsnippet + +snippet code "Code block" b +\`\`\`$1 +${2:${VISUAL}} +\`\`\` +$0 +endsnippet + +snippet detail "Detail block" b +<details> +<summary>$1<summary> + +${2:${VISUAL}} + +</details> +$0 +endsnippet diff --git a/.vim/UltiSnips/tex.snippets b/.vim/UltiSnips/tex.snippets new file mode 100644 index 0000000..9fb2880 --- /dev/null +++ b/.vim/UltiSnips/tex.snippets @@ -0,0 +1,31 @@ +snippet beg "begin{} / end{}" bA +\begin{$1} +$0 +\end{$1} +endsnippet + +snippet mk "Math" wA +$${1}$ $0 +endsnippet +snippet dm "Math" iA +\[ + ${1:${VISUAL}} +.\] $0 +endsnippet + +snippet // "Fraction" iA +\\frac{$1}{$2}$0 +endsnippet + +snippet fun "Function" wA +f: \R \to \R +endsnippet +snippet sum "Sum" wA +\sum_{n=1}^{${1:\infty}} $0 +endsnippet +snippet !> "Maps to" wA +\mapsto +endsnippet +snippet -> "To" wA +\to +endsnippet diff --git a/.vim/UltiSnips/zettel.snippets b/.vim/UltiSnips/zettel.snippets new file mode 100644 index 0000000..4fb343e --- /dev/null +++ b/.vim/UltiSnips/zettel.snippets @@ -0,0 +1,3 @@ +snippet h "hyperlink" +[[$1]]$0 +endsnippet diff --git a/.vim/after/ftplugin/c.vim b/.vim/after/ftplugin/c.vim new file mode 100644 index 0000000..85ff8a2 --- /dev/null +++ b/.vim/after/ftplugin/c.vim @@ -0,0 +1,81 @@ +setlocal cindent + +setlocal foldmethod=syntax +setlocal foldlevel=10 + +if exists('g:loaded_gutentags') + let g:gutentags_enabled = 1 +endif + +" toggle between header and implementation in c/cpp +function! Alternate_c() + let file = expand('%') + if match(file, '\.c') > 0 + exe ':e %<.h' + elseif match(file, '\.h') > 0 + exe ':e %<.c' + endif +endfunction + +nmap <Leader>a :call alternate#alternate() + +" source: https://github.com/igankevich/mesonic/blob/master/plugin/meson.vim +function! MesonProjectDir() + let l:dir = getcwd() " a starting directory to look for meson.build file + let l:project_dir = l:dir " the top-most directory with meson.build file + let l:nesting = 100 " maximal no. of subdirectories in a project + + " find top-most directory with readable meson.build file + while fnamemodify(l:dir, ':p') !=# '/' && l:nesting > 0 + if filereadable(l:dir . '/' . 'meson.build') + let l:project_dir = l:dir + endif + let l:dir = l:dir . '/..' + let l:nesting = l:nesting - 1 + endwhile + + " why one needs to do it two times? + let l:project_dir = fnamemodify(l:project_dir, ':p') + let l:project_dir = fnamemodify(l:project_dir, ':p') + return l:project_dir +endfunction + +if exists('g:loaded_neomake') + function! s:ninja_InitForJob(_jobinfo) dict + " set cwd to project dir + if !has_key(self, 'cwd') + let self.cwd = MesonProjectDir() + endif + " generate new error format lines + " source: https://github.com/igankevich/mesonic/blob/master/compiler/meson.vim#L67 + if !has_key(self, 'errorformat') + let g:meson_error_format = [ + \ '%EMeson encountered an error in file ' . l:project_subdir . '%f\, line %l\, column %c:,%Z%m', + \ '%Dninja: Entering directory `%f''', + \ '%f:%l.%c-%[%^:]%#: %t%[%^:]%#: %m' + \ ] + let self.errorformat = join(g:meson_error_format, ',') + endif + return self + endfunction + function! s:ninja_postprocess(entry) + if empty(a:entry.type) + let a:entry.type = 'E' + endif + endfunction + let g:neomake_c_ninja_maker = { + \ 'exe': 'ninja', + \ 'args': ['-C', 'build'], + \ 'append_file': 0, + \ 'postprocess': function('s:ninja_postprocess'), + \ 'InitForJob': function('s:ninja_InitForJob'), + \ } + let g:neomake_c_ninjaclean_maker = { + \ 'exe': 'ninja', + \ 'args': ['-C', 'build', 'clean'], + \ 'append_file': 0, + \ 'postprocess': function('s:ninja_postprocess'), + \ 'InitForJob': function('s:ninja_InitForJob'), + \ } + let g:neomake_c_enabled_makers = ['ninja'] +endif diff --git a/.vim/after/ftplugin/crontab.vim b/.vim/after/ftplugin/crontab.vim new file mode 100644 index 0000000..46faa33 --- /dev/null +++ b/.vim/after/ftplugin/crontab.vim @@ -0,0 +1 @@ +autocmd filetype crontab setlocal nobackup nowritebackup diff --git a/.vim/after/ftplugin/diff.vim b/.vim/after/ftplugin/diff.vim new file mode 100644 index 0000000..49974ec --- /dev/null +++ b/.vim/after/ftplugin/diff.vim @@ -0,0 +1,33 @@ +" disable annoying plugins +let b:coc_enabled = 0 +silent NeomakeDisable + +command! FindMergeMarker execute 'normal /^<<<<<<</<CR>' + +" shortcuts for 3-way merges +command! DiffGetLocal :diffget LOCAL +command! DiffGetRemote :diffget REMOTE +" quicksave +command! DiffSave :wqa + +cnoreabbrev fmm FindMergeMarker +cnoreabbrev dgl DiffGetLocal +cnoreabbrev dgr DiffGetRemote +cnoreabbrev ds DiffSave + + +let s:markers = { + \ 'ours': '^<<<<<<< ', + \ 'theirs': '^>>>>>>> ', + \ 'base': '^||||||| ', + \ 'delimiter': '^=======\r\?$', + \ } + +if &diff + " set custom ctags in diff mode + + let _ft=&filetype + let g:vista_ctags_cmd = { + \ _ft: 'ctags --format=2 --excmd=pattern --fields=nksSaf --file-scope=yes --sort=no --append=no --output-format=json --fields=-PF -f-', + \ } +endif diff --git a/.vim/after/ftplugin/gitrebase.vim b/.vim/after/ftplugin/gitrebase.vim new file mode 100644 index 0000000..e97d225 --- /dev/null +++ b/.vim/after/ftplugin/gitrebase.vim @@ -0,0 +1,8 @@ +set keywordprg=:GitShow +command! -nargs=1 -bar GitShow call s:git_show(<f-args>) + +function! s:git_show(commit) abort + let p = popup#new({ 'filetype': 'diff' }) + let p.contents = systemlist("git show ".a:commit) + call p.open() +endfunction diff --git a/.vim/after/ftplugin/go.vim b/.vim/after/ftplugin/go.vim new file mode 100644 index 0000000..b5c0d44 --- /dev/null +++ b/.vim/after/ftplugin/go.vim @@ -0,0 +1,77 @@ +setlocal noexpandtab +setlocal shiftwidth=8 +setlocal tabstop=8 + +let g:go_echo_go_info = 0 +let g:go_auto_type_info = 0 +" let g:go_info_mode = 'gocode' +" let g:go_auto_sameids = 1 +" let g:go_gocode_unimported_packages = 0 +" let g:go_list_type = 'quickfix' +let g:go_template_autocreate = 0 + +let g:go_mod_fmt_autosave = 1 +let g:go_fmt_autosave = 1 +let g:go_fmt_command = 'goimports' +let g:go_fmt_fail_silently = 1 +let g:go_fmt_experimental = 1 " solves https://github.com/fatih/vim-go/issues/502 + +" let g:go_term_enabled = 0 +" let g:go_term_mode = 'split' + +let g:go_metalinter_autosave = 0 +let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck'] +let g:go_metalinter_autosave_enabled = ['vet', 'golint'] +let g:go_metalinter_deadline = '2s' + +" let g:go_highlight_functions = 1 +" let g:go_highlight_methods = 1 +" let g:go_highlight_fields = 1 +" let g:go_highlight_structs = 1 +" let g:go_highlight_interfaces = 1 +" let g:go_highlight_build_constraints = 1 +" let g:go_highlight_types = 1 +" let g:go_highlight_extra_types = 1 +" let g:go_highlight_operators = 1 +" let g:go_highlight_space_tab_error = 1 +" let g:go_highlight_array_whitespace_error = 1 +" let g:go_highlight_trailing_whitespace_error = 1 + +function! Alternate_go() + call <Plug>(go-alternate-edit) +endfunction + +" augroup go +" au! +" au VimEnter go call vista#RunForNearestMethodOrFunction() +" augroup END + +if exists('g:loaded_neomake') + let g:neomake_go_build_maker = { + \ 'exe': 'go', + \ 'args': 'build', + \ 'append_file': 0, + \ } + " let g:neomake_go_golangci_lint_args = ['run', '--out-format=line-number', '--print-issued-lines=false'] + let g:neomake_go_enabled_makers = ['go', 'golangci_lint'] +endif + +" https://github.com/fatih/dotfiles/blob/master/vimrc#L380 +" create a go doc comment based on the word under the cursor +function! s:create_go_doc_comment() + norm "zyiw + execute ":put! z" + execute ":norm I// \<Esc>$" +endfunction +nnoremap <Leader>ui :<C-u>call <SID>create_go_doc_comment()<CR> + +" let b:deoplete_disable_auto_complete = 0 +" let g:neoformat_enabled_go = [] +" + +if exists('g:did_coc_loaded') + augroup CoC + au! + au BufWritePre * CocCommand editor.action.organizeImport + augroup END +endif diff --git a/.vim/after/ftplugin/gotmpl.vim b/.vim/after/ftplugin/gotmpl.vim new file mode 100644 index 0000000..cf481c4 --- /dev/null +++ b/.vim/after/ftplugin/gotmpl.vim @@ -0,0 +1 @@ +set commentstring=<!--\ %s\ --> diff --git a/.vim/after/ftplugin/i3.vim b/.vim/after/ftplugin/i3.vim new file mode 100644 index 0000000..9c2e80a --- /dev/null +++ b/.vim/after/ftplugin/i3.vim @@ -0,0 +1 @@ +set commentstring=#\ %s diff --git a/.vim/after/ftplugin/ledger.vim b/.vim/after/ftplugin/ledger.vim new file mode 100644 index 0000000..0aa29a3 --- /dev/null +++ b/.vim/after/ftplugin/ledger.vim @@ -0,0 +1 @@ +let g:ledger_bin = 'hledger' diff --git a/.vim/after/ftplugin/mail.vim b/.vim/after/ftplugin/mail.vim new file mode 100644 index 0000000..108cbf7 --- /dev/null +++ b/.vim/after/ftplugin/mail.vim @@ -0,0 +1,22 @@ +" backup aerc mails to a draft directory +let g:mail_draft_dir = $HOME.'/.aerc/drafts' + +function! SaveDraft() + if !isdirectory(g:mail_draft_dir) + call mkdir(g:mail_draft_dir, 'p') + endif + let draft = join([g:mail_draft_dir, expand('%:t')], '/') + execute 'silent! write! '.draft +endfunction + +function! RestoreFromDraft() + let draft = join([g:mail_draft_dir, expand('%:t')], '/') + echoerr 'todo' +endfunction + +augroup vim_mail + au! + au CursorHold * call SaveDraft() +augroup END + +set commentstring=>\ %s diff --git a/.vim/after/ftplugin/make.vim b/.vim/after/ftplugin/make.vim new file mode 100644 index 0000000..840c87a --- /dev/null +++ b/.vim/after/ftplugin/make.vim @@ -0,0 +1,2 @@ +" Disable tabs->spaces for Makefiles +autocmd filetype make setlocal noexpandtab diff --git a/.vim/after/ftplugin/markdown.vim b/.vim/after/ftplugin/markdown.vim new file mode 100644 index 0000000..0d42ff0 --- /dev/null +++ b/.vim/after/ftplugin/markdown.vim @@ -0,0 +1,27 @@ +setlocal foldlevel=1 + +if exists('g:loaded_lexima') + let g:lexima_enable_space_rules=0 +endif + +function! s:markdown_underline(bang) + call append(line('.'), substitute(getline('.'), '.', (a:bang ? '-' : '='), 'g')) +endfunction +command! -nargs=0 -bang Underline call s:markdown_underline(<bang>0) + +if exists('g:loaded_neomake') + let g:neomake_markdown_shiba_maker = { + \ 'exe': 'shiba', + \ 'args': ['--detach', '%:p'], + \ 'append_file': 0, + \ } + let g:neomake_markdown_enabled_makers = [] +endif + +augroup Markdown + au! + " au FileType markdown NeomakeDisableBuffer + + " Make vim-markdown-folding ignore frontmatter blocks + au BufNewFile,BufRead markdown syntax match Comment /\%^---\_.\{-}---$/ +augroup END diff --git a/.vim/after/ftplugin/python.vim b/.vim/after/ftplugin/python.vim new file mode 100644 index 0000000..667d22e --- /dev/null +++ b/.vim/after/ftplugin/python.vim @@ -0,0 +1,3 @@ +let g:neomake_python_enabled_makers = [] +let g:neoformat_enabled_python = ["yapf"] + diff --git a/.vim/after/ftplugin/rust.vim b/.vim/after/ftplugin/rust.vim new file mode 100644 index 0000000..9bf2ca9 --- /dev/null +++ b/.vim/after/ftplugin/rust.vim @@ -0,0 +1,72 @@ +set tags='./rusty-tags.vi/' + +if exists('g:loaded_lexima') + call lexima#add_rule({'filetype': ['rust'], 'char': "'", 'at': '<\%# | &\%#'}) +endif + +let g:racer_cmd = $HOME.'/.cargo/bin/racer' + +" let g:gutentags_enabled = 0 +" let g:gutentags_define_advanced_commands = 1 +" let g:gutentags_ctags_executable_rust = 'rusty-tags' +" let g:gutentags_ctags_extra_args = ['--omit-deps', 'vi'] +" let g:gutentags_ctags_tagfile = 'rusty-tags.vi' + +" when using standard ctags +let g:tagbar_type_rust = { + \ 'ctagstype' : 'rust', + \ 'kinds' : [ + \'T:types,type definitions', + \'f:functions,function definitions', + \'g:enum,enumeration names', + \'s:structure names', + \'m:modules,module names', + \'c:consts,static constants', + \'t:traits', + \'i:impls,trait implementations', + \] + \} + +" when using universal-ctags +" let g:rust_use_custom_ctags_defs = 1 " if using rust.vim +" let g:tagbar_type_rust = { +" \ 'ctagstype' : 'rust', +" \ 'kinds' : [ +" \ 'n:modules', +" \ 's:structures:1', +" \ 'i:interfaces', +" \ 'c:implementations', +" \ 'f:functions:1', +" \ 'g:enumerations:1', +" \ 't:type aliases:1:0', +" \ 'v:constants:1:0', +" \ 'M:macros:1', +" \ 'm:fields:1:0', +" \ 'e:enum variants:1:0', +" \ 'P:methods:1', +" \ ], +" \ 'sro': '::', +" \ 'kind2scope' : { +" \ 'n': 'module', +" \ 's': 'struct', +" \ 'i': 'interface', +" \ 'c': 'implementation', +" \ 'f': 'function', +" \ 'g': 'enum', +" \ 't': 'typedef', +" \ 'v': 'variable', +" \ 'M': 'macro', +" \ 'm': 'field', +" \ 'e': 'enumerator', +" \ 'P': 'method', +" \ }, +" \ } + +let g:neomake_rust_enabled_makers = ['cargo'] + +" augroup rust +" au! +" augroup END + +" nmap <Leader>d <Plug>(rust-doc) +" nmap <Leader>j <Plug>(rust-def) diff --git a/.vim/after/ftplugin/snippets.vim b/.vim/after/ftplugin/snippets.vim new file mode 100644 index 0000000..cb39fa6 --- /dev/null +++ b/.vim/after/ftplugin/snippets.vim @@ -0,0 +1 @@ +set list diff --git a/.vim/after/ftplugin/tex.vim b/.vim/after/ftplugin/tex.vim new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/.vim/after/ftplugin/tex.vim diff --git a/.vim/after/ftplugin/toml.vim b/.vim/after/ftplugin/toml.vim new file mode 100644 index 0000000..03bf098 --- /dev/null +++ b/.vim/after/ftplugin/toml.vim @@ -0,0 +1,6 @@ +" let g:neoformat_toml_tomlfix = { +" \ 'exe': 'tomlfix', +" \ 'args': ['-w'], +" \ 'replace': 1, +" \ } +" let g:neoformat_enabled_toml = ['tomlfix'] diff --git a/.vim/after/ftplugin/vim.vim b/.vim/after/ftplugin/vim.vim new file mode 100644 index 0000000..a6014c7 --- /dev/null +++ b/.vim/after/ftplugin/vim.vim @@ -0,0 +1 @@ +set foldmethod=marker diff --git a/.vim/after/plugin/clap.vim b/.vim/after/plugin/clap.vim new file mode 100644 index 0000000..b5d48db --- /dev/null +++ b/.vim/after/plugin/clap.vim @@ -0,0 +1,8 @@ +if !exists('g:loaded_clap') + finish +endif + +let g:clap_selected_sign = { 'text': '* ', 'texthl': "WarningMsg", "linehl": "ClapSelected" } + +nnoremap <leader>cc :Clap<CR> +nmap <leader>cg :Clap grep<CR> diff --git a/.vim/after/plugin/coc.vim b/.vim/after/plugin/coc.vim new file mode 100644 index 0000000..2ebe27a --- /dev/null +++ b/.vim/after/plugin/coc.vim @@ -0,0 +1,84 @@ +" coc json config -> ../../coc-settings.json +if !exists('g:did_coc_loaded') + finish +endif + +set completeopt=noinsert,menuone,noselect +set completeopt-=preview +set shortmess+=c +" set showmatch + +" let g:coc_auto_copen = 1 +let g:coc_snippet_next = '<Tab>' +let g:coc_snippet_prev = '<S-Tab>' + +function! s:check_back_space() abort + let col = col('.') - 1 + return !col || getline('.')[col - 1] =~# '\s' +endfunction + +" inoremap <silent><expr> <Tab> +" \ pumvisible() ? "\<C-n>" : +" \ <SID>check_back_space() ? "\<Tab>" : +" \ coc#refresh() +inoremap <silent><expr> <Tab> + \ pumvisible() ? coc#_select_confirm() : + \ coc#expandableOrJumpable() ? "\<C-r>=coc#rpc#request('doKeymap', ['snippets-expand-jump',''])\<CR>" : + \ <SID>check_back_space() ? "\<Tab>" : + \ coc#refresh() + +inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<C-h>" +inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<C-g>u\<CR>" +inoremap <silent><expr> <C-Space> coc#refresh() + +vmap <Tab> <Plug>(coc-snippets-select) + +nmap <silent> [c <Plug>(coc-diagnostic-next) +nmap <silent> ]c <Plug>(coc-diagnostic-prev) +nmap <leader>lc :CocList diagnostics<CR> + +imap <silent> <C-p> <Plug>(coc-complete-custom) +nmap <leader>d <Plug>(coc-definition) +nmap <leader>t <Plug>(coc-type-definition) +nmap <leader>i <Plug>(coc-implementation) +nmap <leader>r <Plug>(coc-references) +nmap <leader>rn <Plug>(coc-rename) +nmap <leader>qf <Plug>(coc-fix-current) +nmap <leader>ac <Plug>(coc-codeaction) + +xmap <leader>f <Plug>(coc-format-selected) +nmap <leader>f <Plug>(coc-format-selected) + +xmap <leader>a <Plug>(coc-codeaction-current) +nmap <leader>a <Plug>(coc-codeaction-current) +nmap <leader>ac <Plug>(coc-codeaction) + +nmap <Leader>o :CocList outline<CR> + +function! s:show_doc() + if (index(['vim', 'help'], &filetype) >= 0) + execute 'h ' . expand('<cword>') + else + call CocAction('doHover') + endif +endfunction +nmap <Leader>i :silent! call <SID>show_doc()<CR> + +au CursorHold * silent call CocActionAsync('highlight') + +command! -nargs=0 Format :call CocAction('format') +command! -nargs=? Fold :call CocAction('fold', <f-args>) + +let g:lightline.component_function.coc = 'LightLineCocStatus' +function! LightLineCocStatus() + let info = get(b:, 'coc_diagnostic_info', {}) + if empty(info) | return '' | endif + let msgs = [] + if get(info, 'error', 0) + call add(msgs, '✘ ' . info['error']) + endif + if get(info, 'warning', 0) + call add(msgs, '‼ ' . info['warning']) + endif + return 'coc: ' . join(msgs, ' ') +endfunction diff --git a/.vim/after/plugin/line_bufferline.vim b/.vim/after/plugin/line_bufferline.vim new file mode 100644 index 0000000..551790f --- /dev/null +++ b/.vim/after/plugin/line_bufferline.vim @@ -0,0 +1,30 @@ +if !exists('g:loaded_bufferline') + finish +endif + +if !exists('g:lightline.tabline.left') + let g:lightline.tabline.left = [] +endif +let g:lightline.tabline.left += [['bufferline']] +let g:lightline.component_expand.bufferline = 'LightLineBufferline' +let g:lightline.component_type.bufferline = 'tabsel' + +function! LightLineBufferline() + call bufferline#refresh_status() + let l:buffers = [ + \ g:bufferline_status_info.before, + \ g:bufferline_status_info.current, + \ g:bufferline_status_info.after + \ ] + + function! s:fmt(key, val) + let limit = 25 + if strlen(a:val) > limit + return trim(a:val)[:limit] . '..' + endif + return trim(a:val) + endfunction + + call map(l:buffers, function('s:fmt')) + return l:buffers +endfunction diff --git a/.vim/after/plugin/line_obsession.vim b/.vim/after/plugin/line_obsession.vim new file mode 100644 index 0000000..c4f774f --- /dev/null +++ b/.vim/after/plugin/line_obsession.vim @@ -0,0 +1,10 @@ +if !exists('g:loaded_obsession') + finish +endif + +let g:lightline.component_function.obsession = 'LightLineSession' + +function! LightLineSession() + let l:indicator = ObsessionStatus('active', 'paused') + return l:indicator ? '[obs '.l:indicator.']' : '' +endfunction diff --git a/.vim/after/plugin/lsp.vim b/.vim/after/plugin/lsp.vim new file mode 100644 index 0000000..d31886a --- /dev/null +++ b/.vim/after/plugin/lsp.vim @@ -0,0 +1,22 @@ +" if !has('nvim-0.5.0') +" echoerr 'nvim_lsp only works with neovim>=0.5.0' +" finish +" endif +finish + +silent LspInstall bashls +silent LspInstall cssls + +" call nvim_lsp#setup("gopls", {}) +call nvim_lsp#setup("pyls", {}) +call nvim_lsp#setup("rust_analyzer", {}) +call nvim_lsp#setup("texlab", {}) + +autocmd Filetype bash,shell,css,go,python,rust,tex,latex setl omnifunc=lsp#omnifunc + +nnoremap <silent> gd :call lsp#text_document_definition()<CR> +nnoremap <silent> gdc :call lsp#text_document_declaration()<CR> +nnoremap <silent> gt :call lsp#text_document_type_definition()<CR> +nnoremap <silent> gi :call lsp#text_document_implementation()<CR> +nnoremap <silent> ;h :call lsp#text_document_hover()<CR> +nnoremap <silent> ;s :call lsp#text_document_signature_help()<CR> diff --git a/.vim/after/plugin/neomake.vim b/.vim/after/plugin/neomake.vim new file mode 100644 index 0000000..7024408 --- /dev/null +++ b/.vim/after/plugin/neomake.vim @@ -0,0 +1,60 @@ +if !exists('g:loaded_neomake') + finish +endif + +" Neomake +cnoreabbrev neom Neomake +cnoreabbrev nm Neomake +call neomake#configure#automake('w') + +" \ 'BufWritePost': {'delay': 0}, +let g:neomake_error_sign = {'text': '✘', 'texthl': 'NeomakeErrorSign'} +let g:neomake_warning_sign = {'text': '‼', 'texthl': 'NeomakeWarningSign'} +let g:neomake_message_sign = {'text': '☛', 'texthl': 'NeomakeMessageSign'} +let g:neomake_info_sign = {'text': 'i', 'texthl': 'NeomakeInfoSign'} +" let g:neomake_error_sign = {'text': '✘', 'texthl': 'ALEErrorSign'} +" let g:neomake_warning_sign = {'text': '‼', 'texthl': 'ALEWarningSign'} +" let g:neomake_message_sign = {'text': '☛', 'texthl': 'ALEInfoSign'} +" let g:neomake_info_sign = {'text': 'i', 'texthl': 'ALEInfoSign'} +" let g:neomake_serialize = 1 + +let g:neomake_highlight_columns = 1 +let g:neomake_highlight_lines = 1 +let g:neomake_open_list = 0 + +" function! OnNeomakeJobFinished() +" let l:ctx = g:neomake_hook_context +" if l:ctx.jobinfo.exit_code != 0 +" echoerr l:ctx.jobinfo.maker.name . ' failed' +" endif +" endfunction +augroup neomake_hooks + au! + " au User NeomakeJobFinished call OnNeomakeJobFinished() + au BufWritePost * Neomake +augroup END + +" generic makers +let g:neomake_makeclean_maker = { 'exe': 'make', 'args': ['clean'], 'errorformat': '%f:%l:%c: %m' } +let g:neomake_make_maker = { 'exe': 'make', 'errorformat': '%f:%l:%c: %m' } + +nmap <leader>nm :Neomake<space> + +let g:lightline.component_function.neomake = 'LightLineNeomake' +function! LightLineNeomake() + let l:msg = neomake#statusline#get(bufnr('%'), { + \ 'format_status_disabled': '{{disabled_info}} %s', + \ 'format_status_enabled': '%s', + \ 'format_running': '… {{running_job_names}}', + \ 'format_loclist_ok': ' ✔', + \ 'format_loclist_issues': '%s', + \ 'format_loclist_unknown': '', + \ 'format_loclist_type_E': ' ✘ {{count}}', + \ 'format_loclist_type_W': ' ‼ {{count}}', + \ 'format_loclist_type_I': ' i {{count}}', + \ 'format_quickfix_type_E': '✘ {{count}}', + \ 'format_quickfix_type_W': '‼ {{count}}', + \ 'format_quickfix_type_I': 'i {{count}}', + \ }) + return l:msg ? 'nm:'.l:msg : '' +endfunction diff --git a/.vim/after/plugin/writing.vim b/.vim/after/plugin/writing.vim new file mode 100644 index 0000000..d868304 --- /dev/null +++ b/.vim/after/plugin/writing.vim @@ -0,0 +1,108 @@ +" Configuration for writing text in vim + +" Grammer checker +let g:grammarous#languagetool_cmd = 'languagetool' + +" Minimal UX for concentration +let g:goyo_width = 120 +let g:goyo_height = 90 +let g:goyo_linenr = 0 +nmap <F0> :Goyo<CR> + +let g:limelight_default_coefficient = 0.5 +let g:limelight_paragraph_span = 1 +" let g:limelight_default_coefficient = 0.5 +" let g:limelight_paragraph_span = 1 +let g:limelight_priority = -1 + +" goyo enter/leave funcs {{{ +function! s:goyo_enter() + if has('gui_running') || has('gui_vimr') + " set linespace = 7 + + elseif exists('$TMUX') + silent !tmux set status off + endif + + " set noshowmode + " set noshowcmd + set scrolloff=999 + Limelight + + " make sure :q in the last buffer, in Goyo also quits vim + let b:quitting = 0 + let b:quitting_bang = 0 + autocmd QuitPre <buffer> let b:quitting = 1 + cabbrev <buffer> q! let b:quitting_bang = 1 <bar> q! +endfunction + +function! s:goyo_leave() + if has('gui_running') || has('gui_vimr') + " set linespace = 0 + + elseif exists('$TMUX') + silent !tmux set status on + endif + + " set showmode + " set showcmd + set scrolloff=5 + Limelight! + + " make sure :q in the last buffer, in Goyo also quits vim + if b:quitting && len(filter(range(1, bufnr('$')), 'buflisted(v:val)')) == 1 + if b:quitting_bang + qa! + else + qa + endif + endif +endfunction +" }}} + +" writing autocommands +function! s:writing_mode_enter() + set textwidth=120 + " ALEToggle + " Grammarous keymaps + nmap <Leader>c :GrammarousCheck --lang=de + nmap <Leader>gi <Plug>(grammarous-move-to-info-window) + nmap <Leader>gr <Plug>(grammarous-remove-error) + nmap <Leader>gd <Plug>(grammarous-disable-rule) + nmap <Leader>gf <Plug>(grammarous-fixit) + nmap <Leader>gn <Plug>(grammarous-move-to-next-error) + nmap <Leader>gp <Plug>(grammarous-move-to-previous-error) +endfunction + +augroup Writing + au! + au FileType markdown,tex call <SID>writing_mode_enter() + " goyo autocommands + au User GoyoEnter nested call <SID>goyo_enter() + au User GoyoLeave nested call <SID>goyo_leave() +augroup END + +" LaTeX +" let g:vimtex_view_general_viewer +" \ = '/Applications/Skim.app/Contents/SharedSupport/displayline' +" let g:vimtex_view_general_options = '-r @line @pdf @tex' + +" " This adds a callback hook that updates Skim after compilation +" let g:vimtex_latexmk_callback_hooks = ['UpdateSkim'] +" function! UpdateSkim(status) +" if !a:status | return | endif + +" let l:out = b:vimtex.out() +" let l:tex = expand('%:p') +" let l:cmd = [g:vimtex_view_general_viewer, '-r'] +" if !empty(system('pgrep Skim')) +" call extend(l:cmd, ['-g']) +" endif +" if has('nvim') +" call jobstart(l:cmd + [line('.'), l:out, l:tex]) +" elseif has('job') +" call job_start(l:cmd + [line('.'), l:out, l:tex]) +" else +" call system(join(l:cmd + [line('.'), shellescape(l:out), shellescape(l:tex)], ' ')) +" endif +" endfunction diff --git a/.vim/autoload/alternate.vim b/.vim/autoload/alternate.vim new file mode 100644 index 0000000..e179fce --- /dev/null +++ b/.vim/autoload/alternate.vim @@ -0,0 +1,8 @@ +function! alternate#alternate() + let l:function_name = 'Alternate_' . &filetype + if exists('*' . l:function_name) + execute l:function_name + else + echo 'no alternate function defined for "' . &filetype . '"' + endif +endfunction diff --git a/.vim/autoload/daymode.vim b/.vim/autoload/daymode.vim new file mode 100644 index 0000000..afac142 --- /dev/null +++ b/.vim/autoload/daymode.vim @@ -0,0 +1,34 @@ +function! s:lightline_update(colors) + if !exists('g:loaded_lightline') + return + endif + try + let g:lightline.colorscheme = a:colors + call lightline#init() + call lightline#colorscheme() + call lightline#update() + catch + endtry +endfunction + +let s:day_mode_previous_colorscheme = '' +let s:day_mode_previous_lightline_colorscheme = '' + +function! daymode#switch(bang) + if a:bang + set background=dark + execute 'colorscheme' s:day_mode_previous_colorscheme + call s:lightline_update(s:day_mode_previous_lightline_colorscheme) + else + let s:day_mode_previous_colorscheme = g:colors_name + let s:day_mode_previous_lightline_colorscheme = g:lightline.colorscheme + + set background=light + execute 'colorscheme' get(g:, 'day_mode_colorscheme', 'morning') + call s:lightline_update(get(g:, 'day_mode_lightline_colorscheme', 'PaperColor_light')) + endif + + if exists("syntax_on") + syntax reset + endif +endfunction diff --git a/.vim/autoload/fasd.vim b/.vim/autoload/fasd.vim new file mode 100644 index 0000000..7ef53bd --- /dev/null +++ b/.vim/autoload/fasd.vim @@ -0,0 +1,18 @@ +" not sure if I am using this +finish + +if exists('g:loaded_fasd') + finish +endif +let g:loaded_fasd = 1 +let g:fasd_path = '/home/robert/.dotfiles/zsh/.zplug/repos/sorin-ionescu/prezto/modules/fasd/external/fasd' + +function! fasd#run(args) abort + return system(g:fasd_path . ' -d -0 ' . a:args) +endfunction + +function! fasd#cd(args) abort + execute 'tcd' . fasd#run(a:args) +endfunction + +command! -nargs=1 J :call fasd#cd(<f-args>) diff --git a/.vim/autoload/fuzz.vim b/.vim/autoload/fuzz.vim new file mode 100644 index 0000000..39d02f1 --- /dev/null +++ b/.vim/autoload/fuzz.vim @@ -0,0 +1,140 @@ +function! s:prompt(str) + return a:str . g:fuzz_prompt . ' ' +endfunction + +function! s:fuzz(name, opts) + " call skim#run(skim#wrap(a:name, a:opts, 0)) + call fzf#run(fzf#wrap(a:name, a:opts, 0)) +endfunction + +function! s:strip(str) + return substitute(a:str, '^\s*\|\s*$', '', 'g') +endfunction + +function! s:open(cmd, target) + if stridx('edit', a:cmd) == 0 && fnamemodify(a:target, ':p') ==# expand('%:p') + return + endif + execute a:cmd fnameescape(a:target) +endfunction + + +" rg {{{ +function s:map_rg_output(line) + let parts = split(a:line, ':') + return {'filename': &acd ? fnamemodify(parts[0], ':p') : parts[0], 'lnum': parts[1], 'text': join(parts[2:], ':')} +endfunction + +function s:rg_handler(lines) + if len(a:lines) < 1 + return + endif + let first = map(filter(a:lines, 'len(v:val)'), 's:map_rg_output(v:val)')[0] + call s:open('edit', first.filename) + execute first.lnum + normal! zz +endfunction + +function s:rg(prompt, ...) + if !executable('rg') + echoerr 'rg not found' + return + endif + if len(a:000) < 1 || len(a:000[0]) < 1 + echoerr 'no arguments' + return + endif + + call s:fuzz(!empty(a:prompt) ? tolower(a:prompt) : 'rg', { + \ 'source': 'rg --line-number --no-heading --color=always --smart-case '.join(a:000[0], ' '), + \ 'sink*': function('s:rg_handler'), + \ 'options': ['--prompt', s:prompt(!empty(a:prompt) ? a:prompt : 'Rg'), + \ '--ansi', '--multi'] + \ }) +endfunction + +function! fuzz#rg(args) abort + call s:rg('', a:args) +endfunction +" }}} + +" files {{{ +function! fuzz#files() abort + call s:fuzz('files', { + \ 'options': ['--prompt', s:prompt('Fs')] + \ }) +endfunction +" }}} + +" buffers {{{ +function! s:buffers_list() + function! s:buflisted() + return filter(range(1, bufnr('$')), 'buflisted(v:val) && getbufvar(v:val, "&filetype") != "qf"') + endfunction + + function! s:format_buffer(b) + let name = bufname(a:b) + let name = empty(name) ? '[No Name]' : fnamemodify(name, ":p:~:.") + let flag = a:b == bufnr('') ? '%' : + \ (a:b == bufnr('#') ? '#' : ' ') + let modified = getbufvar(a:b, '&modified') ? ' [+]' : '' + let readonly = getbufvar(a:b, '&modifiable') ? '' : ' [RO]' + let extra = join(filter([modified, readonly], '!empty(v:val)'), '') + return s:strip(printf("[%s] %s\t%s\t%s", a:b, flag, name, extra)) + endfunction + + return map(sort(s:buflisted()), 's:format_buffer(v:val)') +endfunction + +function! s:buffers_handler(lines) + if len(a:lines) < 1 + return + endif + let b = matchstr(a:lines[0], '\[\zs[0-9]*\ze\]') + execute 'buffer' b +endfunction + +function! fuzz#buffers() abort + call s:fuzz('buffers', { + \ 'source': s:buffers_list(), + \ 'sink*': function('s:buffers_handler'), + \ 'options': ['--prompt', s:prompt('Buf')] + \ }) +endfunction +" }}} + +" commands {{{ +function! s:command_handler(lines) + if len(a:lines) < 1 + return + endif + call feedkeys(':'.a:lines[0]) +endfunction + +function! fuzz#commands() abort + call s:fuzz('commands', { + \ 'source': sort(getcompletion('', 'command')), + \ 'sink*': function('s:command_handler'), + \ 'options': ['--prompt', s:prompt('Cmd')] + \ }) +endfunction +" }}} + +function! fuzz#cmdoptions(...) abort + let opts = sort(['files', 'buffers', 'commands', 'rg']) + return join(opts, "\n") +endfunction + +function! fuzz#exec(cmd, ...) abort + if a:cmd == 'files' + call fuzz#files() + elseif a:cmd == 'buffers' + call fuzz#buffers() + elseif a:cmd == 'commands' + call fuzz#commands() + elseif a:cmd == 'rg' + call fuzz#rg(a:000) + else + echoerr 'Unknown command ' . a:cmd + endif +endfunction diff --git a/.vim/autoload/lightline/colorscheme/papercolor_dim.vim b/.vim/autoload/lightline/colorscheme/papercolor_dim.vim new file mode 100644 index 0000000..6398cdb --- /dev/null +++ b/.vim/autoload/lightline/colorscheme/papercolor_dim.vim @@ -0,0 +1,56 @@ +" papercolor_dim : a less flashy statusline +" +" Adapted from the Papercolor_dark theme: +" ============================================================================= +" Filename: autoload/lightline/colorscheme/PaperColor_dark.vim +" Author: TKNGUE +" License: MIT License +" Last Change: 2015-07-27 06:01 +" ============================================================================= + +let s:red = '#af005f' +let s:green = '#5faf5f' +let s:blue = '#5fafd7' +let s:magenta = '#ff5faf' +let s:olive = '#dfaf5f' +let s:navy = '#df875f' +let s:orange = '#d7875f' +let s:purple = '#af87d7' +let s:aqua = '#00afaf' + +let s:black = '#1c1c1c' +let s:grey_dim = '#3a3a3a' +let s:grey_dimmer = '#262626' + +let s:white = '#d0d0d0' +let s:white_dim = '#bdc0ba' + +" Tabline: +let s:tabline_bg = '#3a3a3a' +let s:tabline_active_fg = '#1c1c1c' +let s:tabline_active_bg = '#00afaf' +let s:tabline_inactive_fg = '#c6c6c6' +let s:tabline_inactive_bg = '#585858' + +let s:p = {'command': {}, 'normal': {}, 'inactive': {}, 'insert': {}, 'replace': {}, 'visual': {}, 'tabline': {}} + +let s:p.normal.left = [[ s:white, s:grey_dim ], [ s:white_dim, s:grey_dimmer ], [ s:white_dim, 'none' ]] +let s:p.normal.right = [[ s:white, s:grey_dim ], [ s:white_dim, s:grey_dimmer ], [ s:white_dim, 'none' ]] +let s:p.normal.middle = [[ s:white_dim, 'none' ]] +let s:p.normal.error = [[ s:black, s:red ]] + +let s:p.inactive.right = [[ s:white_dim, s:grey_dim ], [ s:white_dim, s:grey_dim ]] +let s:p.inactive.right = [[ s:white_dim, s:grey_dim ], [ s:white_dim, s:grey_dim ]] +let s:p.inactive.middle = [[ s:white_dim, s:grey_dim ]] + +let s:p.insert.left = [[ s:black, s:blue ], copy(s:p.normal.left[1]), copy(s:p.normal.left[2])] +let s:p.replace.left = [[ s:black, s:orange ], copy(s:p.normal.left[1]), copy(s:p.normal.left[2])] +let s:p.visual.left = [[ s:black, s:purple ], copy(s:p.normal.left[1]), copy(s:p.normal.left[2])] +" let s:p.command.left = [[ s:black, s:green ], copy(s:p.normal.left[1]), copy(s:p.normal.left[2])] + +let s:p.tabline.left = [[ s:grey_dim, 'none' ]] +let s:p.tabline.tabsel = [[ s:white_dim, 'none' ]] +let s:p.tabline.middle = [[ s:white_dim, 'none' ]] +let s:p.tabline.right = [[ s:white_dim, 'none' ]] + +let g:lightline#colorscheme#papercolor_dim#palette = lightline#colorscheme#fill(s:p) diff --git a/.vim/autoload/numbertoggle.vim b/.vim/autoload/numbertoggle.vim new file mode 100644 index 0000000..fe8ddcc --- /dev/null +++ b/.vim/autoload/numbertoggle.vim @@ -0,0 +1,17 @@ +function! s:relative_linenumbers() + set number + set relativenumber +endfunc + +function! s:absolute_linenumbers() + set number + set norelativenumber +endfunc + +function! numbertoggle#switch() + if &relativenumber + call s:absolute_linenumbers() + else + call s:relative_linenumbers() + endif +endfunc diff --git a/.vim/autoload/plug.vim b/.vim/autoload/plug.vim new file mode 100644 index 0000000..4e05630 --- /dev/null +++ b/.vim/autoload/plug.vim @@ -0,0 +1,2526 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or `<Plug>`-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:me = resolve(expand('<sfile>:p')) +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(fnamemodify(expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#(<args>) + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>]) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>]) + command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! s:lazy(plug, opt) + return has_key(a:plug, a:opt) && + \ (empty(s:to_a(a:plug[a:opt])) || + \ !isdirectory(a:plug.dir) || + \ len(s:glob(s:rtp(a:plug), 'plugin')) || + \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('Call plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^<Plug>.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or `<Plug>`.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +if s:is_win + function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction +else + function! s:rtp(spec) + return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]).'/**' + for dir in ['ftdetect', 'ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\<esc>" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap <silent> <buffer> R :call <SID>retry()<cr> + nnoremap <silent> <buffer> D :PlugDiff<cr> + nnoremap <silent> <buffer> S :PlugStatus<cr> + nnoremap <silent> <buffer> U :call <SID>status_update()<cr> + xnoremap <silent> <buffer> U :call <SID>status_update()<cr> + nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr> + nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr> +endfunction + +function! s:prepare(...) + if empty(getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr> + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap <buffer>' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + if exists('+colorcolumn') + setlocal colorcolumn= + endif + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if s:is_win + set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 + elseif a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(["@echo off\r", cmd . "\r"], batchfile) + let cmd = batchfile + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\<cr>\<cr>" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) + else + let branch = s:esc(get(spec, 'branch', 'master')) + call s:log4(name, 'Merging origin/'.branch) + let out = s:system('git checkout -q '.branch.' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd + if !empty(job.batchfile) + call writefile(["@echo off\r", cmd . "\r"], job.batchfile) + let cmd = job.batchfile + endif + let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + if has_key(job, 'batchfile') && !empty(job.batchfile) + call delete(job.batchfile) + endif + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + for i in range(4, line('$')) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, line('$')) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = empty(globpath(spec.dir, '.git', 1)) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ s:shellesc(spec.uri), + \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') + return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' +endfunction + +function! s:shellesc(arg) + if &shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return shellescape(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir) + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) +endfunction + +function! s:system(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(["@echo off\r", cmd . "\r"], batchfile) + let cmd = batchfile + endif + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@ + nmap <silent> <buffer> dd d_ + xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr> + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr> + xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr> + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let batchfile = tempname().'.bat' + call writefile(["@echo off\r", cmd . "\r"], batchfile) + let cmd = batchfile + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap <silent> <buffer> q :q<cr> + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let cmd = 'git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')) + if has_key(v, 'rtp') + let cmd .= ' -- '.s:shellesc(v.rtp) + endif + let diff = s:system_chomp(cmd, v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr> + if empty(maparg("\<cr>", 'n')) + nmap <buffer> <cr> <plug>(plug-preview) + endif + if empty(maparg('o', 'n')) + nmap <buffer> o <plug>(plug-preview) + endif + endif + if cnts[0] + nnoremap <silent> <buffer> X :call <SID>revert()<cr> + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@<!,') +endfunction + +let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, '')) +let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, '')) + +if exists('g:plugs') + let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs)) + call s:upgrade_specs() + call s:define_commands() +endif + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/.vim/autoload/popup.vim b/.vim/autoload/popup.vim new file mode 100644 index 0000000..e9f27d4 --- /dev/null +++ b/.vim/autoload/popup.vim @@ -0,0 +1,215 @@ +if !(has('nvim') && exists('*nvim_win_set_config')) + echoerr "floating window not supported" + finish +endif + +if exists('g:nvim_popup_loaded') + finish +endif +let g:nvim_popup_loaded = 1 + +let s:popup = {} + +function! s:popup__floating_win_opts(width, height) dict abort + let bottom_line = line('w0') + winheight(0) - 1 + if self.opened_at[1] + a:height <= bottom_line + let vert = 'N' + let row = 1 + else + let vert = 'S' + let row = 0 + endif + if self.opened_at[2] + a:width <= &columns + let hor = 'W' + let col = 0 + else + let hor = 'E' + let col = 1 + endif + return { + \ 'style': 'minimal', + \ 'relative': 'cursor', + \ 'anchor': vert.hor, + \ 'row': row, + \ 'col': col, + \ 'width': a:width, + \ 'height': a:height, + \ } +endfunction +let s:popup.floating_win_opts = funcref('s:popup__floating_win_opts') + +function! s:popup__window_size() dict abort + let max_width = winwidth(0) / 2 + let max_height = winheight(0) / 3 + let width = 0 + let height = 0 + for line in self.contents + let lw = strdisplaywidth(line) + if lw > width + if lw > max_width + let height += lw / popup_max_width + 1 + let width = popup_max_width + continue + endif + let width = lw + endif + let height += 1 + endfor + let width += 1 + + if height > max_height + let height = max_height + endif + + return [width, height] +endfunction +let s:popup.window_size = funcref('s:popup__window_size') + +function! s:popup__get_winnr() dict abort + return win_id2win(self.win_id) +endfunction +let s:popup.get_winnr = funcref('s:popup__get_winnr') + +function! s:popup__get_opener_winnr() dict abort + let winnr = win_id2win(self.opener_winid) + if winnr != 0 + return winnr + endif + let winnr = bufwinnr(self.opener_bufnr) + if winnr > 0 + return winnr + endif + return 0 +endfunction +let s:popup.get_opener_winnr = funcref('s:popup__get_opener_winnr') + +function! s:popup_open() dict abort + let self.opened_at = getpos('.') + let self.opener_bufnr = bufnr('%') + let self.opener_winid = win_getid() + let self.type = 'floating' + + let [w, h] = self.window_size() + + if self.type ==# 'floating' + let opts = self.floating_win_opts(w, h) + let win_id = nvim_open_win(self.opener_bufnr, v:true, opts) + endif + + enew! + let popup_bufnr = bufnr('%') + setlocal + \ buftype=nofile bufhidden=hide nomodified nobuflisted noswapfile nonumber + \ nocursorline wrap nonumber norelativenumber signcolumn=no nofoldenable + \ nospell nolist nomodeline + if has_key(self.opts, 'filetype') + let &l:filetype = self.opts.filetype + endif + + call setline(1, self.contents) + setlocal nomodified nomodifiable + + if has('nvim') + setlocal winhl=Normal:NormalFloat,NormalNC:NormalFloat + endif + + nnoremap <buffer><silent><nowait><Esc> :<C-u>call b:__popup.close()<CR> + nnoremap <buffer><silent><nowait>q :<C-u>call b:__popup.close()<CR> + + let b:__popup = self + execute 'autocmd BufWipeout,BufLeave <buffer> call getbufvar(' . popup_bufnr . ', "__popup").close()' + + if has_key(self.opts, 'enter') && !self.opts.enter + wincmd p + endif + + let self.bufnr = popup_bufnr + let self.win_id = win_id +endfunction +let s:popup.open = funcref('s:popup_open') + +function! s:popup__close() dict abort + if !has_key(self, 'bufnr') + return + endif + + let winnr = self.get_winnr() + if winnr > 0 + noautocmd execute winnr . 'wincmd c' + endif + + unlet self.bufnr + unlet self.win_id +endfunction +let s:popup.close = funcref('s:popup__close') + +function! s:popup__scroll(map) dict abort + let winnr = self.get_winnr() + if winnr == 0 + return + endif + execute winnr . 'wincmd w' + sandbox let input = eval('"\<'.a:map.'>"') + execute 'normal!' input + wincmd p +endfunction +let s:popup.scroll = funcref('s:popup__scroll') + +function! s:popup__update() dict abort + " Note: `:noautocmd` to prevent BufLeave autocmd event (#13) + " It should be ok because the cursor position is finally back to the first + " position. + + let prev_winnr = winnr() + + let popup_winnr = self.get_winnr() + if popup_winnr == 0 + return + endif + let opener_winnr = self.get_opener_winnr() + if opener_winnr == 0 + return + endif + + if opener_winnr != prev_winnr + noautocmd execute opener_winnr . 'wincmd w' + endif + + try + let [width, height] = self.window_size() + + " Window must be configured in opener buffer since the window position + " is relative to cursor + if self.type ==# 'floating' + let id = win_getid(popup_winnr) + if id == 0 + return + endif + let opts = self.floating_win_opts(width, height) + call nvim_win_set_config(id, opts) + endif + + noautocmd execute popup_winnr . 'wincmd w' + + if self.type ==# 'preview' + execute height . 'wincmd _' + endif + + setlocal modifiable + silent %delete _ + call setline(1, self.contents) + setlocal nomodified nomodifiable + finally + if winnr() != prev_winnr + noautocmd execute prev_winnr . 'wincmd w' + endif + endtry +endfunction +let s:popup.update = funcref('s:popup__update') + +function! popup#new(opts) abort + let p = deepcopy(s:popup) + let opts = { 'floating': v:true } + let p.opts = extend(opts, a:opts) + return p +endfunction diff --git a/.vim/autoload/retab.vim b/.vim/autoload/retab.vim new file mode 100644 index 0000000..8cb8759 --- /dev/null +++ b/.vim/autoload/retab.vim @@ -0,0 +1,7 @@ + +func! retab#do() + for lineno in range(a:firstline, a:lastline) + normal! << + normal! >> + endfor +endfunc diff --git a/.vim/autoload/split_term.vim b/.vim/autoload/split_term.vim new file mode 100644 index 0000000..e34fe59 --- /dev/null +++ b/.vim/autoload/split_term.vim @@ -0,0 +1,69 @@ +let s:spt = {} + +function! s:spt_open() dict abort + botright new + exec "resize " . self.opts.height + try + exec "buffer " . self.bufnr + catch + call termopen(self.opts.exec, {"detach": 0}) + let self.bufnr = bufnr('%') + " term style + set nonumber + set norelativenumber + set signcolumn=no + " garbage collect after closing + let b:__split_term = self + execute 'autocmd BufWipeout <buffer> call getbufvar(' . self.bufnr . ', "__split_term").close()' + endtry + startinsert! + let self.winid = win_getid() +endfunction +let s:spt.open = funcref('s:spt_open') + +function! s:spt_hide() dict abort + if win_gotoid(self.winid) + hide + else + echoerr 'split term window not found' + endif +endfunction +let s:spt.hide = funcref('s:spt_hide') + +function! s:spt_close() dict abort + if win_gotoid(self.winid) + close + endif + unlet self.bufnr + unlet self.winid +endfunction +let s:spt.close = funcref('s:spt_close') + +function! split_term#new(opts) abort + let spt = deepcopy(s:spt) + let opts = { + \ 'exec': $SHELL, + \ 'height': 20, + \ } + let spt.opts = extend(opts, a:opts) + return spt +endfunction + +function! s:split_term_new(args) abort + if len(a:args) > 0 + let opts = {'exec': join(a:args, ' ')} + endif + let g:split_term = split_term#new(exists('opts') ? opts : {}) +endfunction +function! s:split_term_toggle(bang, ...) abort + if !a:bang + if !exists('g:split_term') + call s:split_term_new(a:000) + endif + call g:split_term.open() + else + if exists('g:split_term') + call g:split_term.hide() + endif + endif +endfunction diff --git a/.vim/autoload/zettel.vim b/.vim/autoload/zettel.vim new file mode 100644 index 0000000..381b8e6 --- /dev/null +++ b/.vim/autoload/zettel.vim @@ -0,0 +1,37 @@ +" note taking helpers +" to aide in practicing the zettelkasten.de system +" +" ref: https://vimways.org/2019/personal-notetaking-in-vim/ + +let g:zettel_dir = '~/wiki' +let g:zettel_ext = '.md' + +func! zettel#is_zettel(fpath) + if a:fpath =~ g:zettel_dir . '/.*' . g:zettel_ext . '$' | return 1 | en + return 0 +endfunc + +func! zettel#set_filetype() + silent exec 'setlocal filetype=zettel.' . &filetype +endfunc + +func! zettel#edit(...) + let l:sep = '' + if len(a:000) > 0 + " argument is a title for the note + let l:sep = '-' + endif + let l:fname = expand(g:zettel_dir) . '/' + \ . strftime('%Y-%m-%dT%H-%M') + \ . l:sep . join(a:000, l:sep) . g:zettel_ext + + silent exec 'e ' . l:fname + + " if len(a:000) > 0 + " silent exec 'normal gg0\<c-r>=strftime("%Y-%m-%d %H:%M")\<cr> ' + " \ . join(a:000) + " \ . '\<cr>\<esc>G' + " else + " silent exec 'normal gg0\<c-r>=strftime("%Y-%m-%d %H:%M")\<cr>\<cr>\<esc>G' + " endif +endfunc diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json new file mode 100644 index 0000000..6bf350b --- /dev/null +++ b/.vim/coc-settings.json @@ -0,0 +1,82 @@ +{ + "coc.preferences.snippets.enable": true, + "coc.preferences.formatOnType": false, + "coc.preferences.formatOnSaveFiletypes": [], + "coc.preferences.hoverTarget": "float", + "coc.preferences.enableFloatHighlight": true, + "coc.preferences.extensionUpdateCheck": "never", + + "list.signOffset": 900, + + "suggest.autoTrigger": "always", + "suggest.triggerAfterInsertEnter": true, + "suggest.minTriggerInputLength": 1, + "suggest.noselect": true, + "suggest.enablePreview": true, + "suggest.enablePreselect": false, + "suggest.floatEnable": true, + + "signature.enable": true, + "signature.target": "float", + "signature.preferShownAbove": true, + + "diagnostic.enable": true, + "diagnostic.checkCurrentLine": false, + "diagnostic.virtualText": true, + "diagnostic.virtualTextLines": 1, + "diagnostic.messageTarget": "float", + "diagnostic.level": "hint", + "diagnostic.enableSign": true, + "diagnostic.errorSign": "✘", + "diagnostic.warningSign": "‼", + "diagnostic.infoSign": "i", + "diagnostic.hintSign": "☛", + + "rust-client.channel": "stable", + "rust-client.rlsPath": "ra_lsp_server", + + "python.linting.flake8Enabled": false, + "python.linting.pylintEnabled": true, + + "languageserver": { + "gopls": { + "enable": false, + "command": "gopls", + "rootPatterns": ["go.mod", ".git/", ".hg/"], + "filetypes": ["go"] + }, + "go-langserver": { + "enable": false, + "command": "go-langserver", + "initializationOptions": { + "gocodeCompletionEnabled": true, + "diagnosticsEnabled": true, + "lintTool": "golint" + }, + "filetypes": ["go"] + }, + "ccls": { + "enable": false, + "command": "ccls", + "rootPatterns": [".ccls", "compile_commands.json", ".git/", ".hg/"], + "initializationOptions": { + "cache": { + "directory": "/tmp/ccls" + } + }, + "filetypes": ["c", "cpp", "objc", "objcpp"] + }, + "rust-analyzer": { + "enable": false, + "command": "ra_lsp_server", + "rootPatterns": ["Cargo.toml", "Cargo.lock", ".git/"], + "filetypes": ["rust"] + }, + "efm": { + "enable": false, + "command": "efm-langserver", + "args": [], + "filetypes": ["vim", "markdown", "yaml"] + } + } +} diff --git a/.vim/doc/how-to-do-things.md b/.vim/doc/how-to-do-things.md new file mode 100644 index 0000000..a705d62 --- /dev/null +++ b/.vim/doc/how-to-do-things.md @@ -0,0 +1,9 @@ +# vim : how to do things + +## navigation + +### open path under cursor? + +- `gf` _goto file_ +- `<c-w>f` _in new window_ +- `<c-w>gf` \_in new tab` diff --git a/.vim/ftdetect/h.vim b/.vim/ftdetect/h.vim new file mode 100644 index 0000000..665088d --- /dev/null +++ b/.vim/ftdetect/h.vim @@ -0,0 +1 @@ +au BufNewFile,BufEnter *.h :setlocal filetype=c diff --git a/.vim/ftdetect/journal.vim b/.vim/ftdetect/journal.vim new file mode 100644 index 0000000..a636837 --- /dev/null +++ b/.vim/ftdetect/journal.vim @@ -0,0 +1 @@ +au BufNewFile,BufRead *.journal set filetype=ledger diff --git a/.vim/ftdetect/pixy.vim b/.vim/ftdetect/pixy.vim new file mode 100644 index 0000000..2ffb158 --- /dev/null +++ b/.vim/ftdetect/pixy.vim @@ -0,0 +1 @@ +au BufNewFile,BufRead *.pixy set filetype=pug noet diff --git a/.vim/ftdetect/scarlet.vim b/.vim/ftdetect/scarlet.vim new file mode 100644 index 0000000..40ed638 --- /dev/null +++ b/.vim/ftdetect/scarlet.vim @@ -0,0 +1 @@ +au BufNewFile,BufRead *.scarlet set filetype=stylus diff --git a/.vim/ftdetect/zettel.vim b/.vim/ftdetect/zettel.vim new file mode 100644 index 0000000..ecf6a0e --- /dev/null +++ b/.vim/ftdetect/zettel.vim @@ -0,0 +1,4 @@ +augroup zettel_file + au! + au BufNewFile,BufRead * if zettel#is_zettel(expand('%:p')) | call zettel#set_filetype() | en +augroup END diff --git a/.vim/general.vim b/.vim/general.vim new file mode 100644 index 0000000..8fbc18d --- /dev/null +++ b/.vim/general.vim @@ -0,0 +1,65 @@ +" .vimrc#General settings +set exrc +set secure +set title +set noshowmode +set autowrite +set inccommand=split +set noshowcmd +" set autochdir " automatically sets cwd to the dir containing the file in the current buffer +set hidden +set nolist +" set lcs+=space:• +" set lcs+=trail:∙ +set encoding=utf-8 nobomb +" set nocompatible +set number +let &showbreak='↪ ' +set visualbell +set noerrorbells +set hlsearch +set smartcase +set ignorecase +set incsearch +" set cmdheight=2 +set pumheight=10 +set lazyredraw +set mouse=a +set clipboard+=unnamedplus " share OSX clipboard +set backspace=indent,eol,start " allow backspace in INSERT mode +set signcolumn=yes +set cursorline +" set cursorcolumn +set colorcolumn=80 +set updatetime=300 + +" For conceal markers. +if has('conceal') + " set conceallevel=2 concealcursor=nv + set conceallevel=2 concealcursor= +endif + +" centralize vimfiles +set backupdir=/tmp +set directory=/tmp +if exists('&undodir') + set undodir=~/.vim/undo +endif + +" pane divider chars +set fillchars=fold:-,vert:\│ + +"" colorscheme options +if has("termguicolors") + set termguicolors +endif + +filetype plugin indent on +if has('syntax') && !exists('g:syntax_on') + syntax enable +endif + +" some defaults +" set ts=8 sts=0 et sw=4 sta + +set diffopt+=algorithm:patience diff --git a/.vim/init.vim b/.vim/init.vim new file mode 100644 index 0000000..879753a --- /dev/null +++ b/.vim/init.vim @@ -0,0 +1,286 @@ +let g:mapleader = ' ' " <leader> key to <space> +let g:maplocalleader = '\' + +runtime general.vim +runtime plug.vim + +" hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white +" hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white +" nnoremap <Leader>cl :set cursorline!<CR> +" nnoremap <Leader>cc :set cursorcolumn!<CR> + +" looks {{{ +set background=dark +let g:PaperColor_Theme_Options = { +\ 'theme': { +\ 'default.dark': { +\ 'allow_bold': 1, +\ 'allow_italic': 1, +\ 'transparent_background': 1, +\ } +\ } +\ } +colorscheme PaperColor +let g:day_mode_colorscheme = 'PaperColor' + +" Make comments italic, no matter what the colorscheme does +hi Comment gui=italic +" Fix ugly looking grey when transparent_background=1 +hi SignColumn guibg=none ctermbg=none +" }}} + +" key bindings {{{ +" speed +noremap j gj +noremap k gk +" because this is annoying +cnoreabbrev W w +cnoreabbrev E e +cnoreabbrev Q q +" Buffer commands +nmap <silent> gb :bnext<CR> +nmap <silent> gB :bprevious<CR> +" quickfix jumping +nnoremap <leader>cn :cnext<CR> +nnoremap <leader>cp :cprevious<CR> +nnoremap <leader>cl :clist<CR> +" loclist jumping +nnoremap <leader>ln :lnext<CR> +nnoremap <leader>lp :lprevious<CR> +nnoremap <leader>ll :llist<CR> +" escape terminal mode with <esc> +tnoremap <esc> <C-\><C-n> +tnoremap <leader>qq <C-\><C-n>:bdelete!<CR> +" delete and don't remember... +" nnoremap s "_d +" nnoremap ss "_dd +" vnoremap s "_d +" replace word under cursor +nmap <silent> riw :%s/\<<C-r><C-w>\>/ + +nmap <silent> ga <Plug>(EasyAlign) +vmap <silent> <Enter> <Plug>(EasyAlign) +nmap <silent> gl <Plug>(OrgHyperlinkFollow) +nmap <leader>gm <Plug>(git-messenger) +nmap <leader>v :Vista!!<CR> +nmap <leader>nt :call numbertoggle#switch()<CR> +nmap <leader>tn :rightbelow Tnew<CR> +vmap rt :call retab#do()<CR> +" }}} + +" alternate -> plugin/alternate.vim +" daymode -> plugin/daymode.vim +" fasd -> plugin/fasd.vim +" fuzzy -> plugin/fuzz.vim +" hardmode -> plugin/hard.vim +" numbertoggle -> plugin/numbertoggle.vim +" writing -> after/plugin/tagbar.vim +" filetype configs -> after/ftplugin +" coc -> after/plugin/coc.vim +" neomake -> after/plugin/neomake.vim +" tagbar -> after/plugin/tagbar.vim +" lightline -> plugin/line.vim + +" plugin config {{{ +" +" obsession {{{2 +let g:session_extension = ".vim-session" +let g:session_autosave = "no" +let g:session_autoload = "no" +let g:session_autosave_periodic = 5 +let g:session_command_aliases = 1 +" }}}2 +let g:echodoc_enable_at_startup = 1 +" matchtagalways {{{2 +let g:mta_use_matchparen_group = 1 +let g:mta_filetypes = { + \ 'html' : 1, + \ 'xhtml' : 1, + \ 'xml' : 1, + \ 'jinja' : 1, + \ 'vue' : 1 + \} +" }}}2 +" emmet +let g:user_emmet_leader_key = g:maplocalleader " followed by , +" let g:user_emmet_mode = 'a' +" indentguides {{{2 +let g:indent_guides_enable_on_vim_startup = 1 +let g:indent_guides_start_level = 2 +let g:indent_guides_color_change_percent = 2 +let g:indent_guides_guide_size = 1 +" }}}2 +" indentline {{{2 +" let g:indentLine_char = '▏' +" let g:indentLine_enabled = 1 +" let g:indentLine_conceallevel = 1 +" :set list lcs=tab:\▏\ +" }}}2 +" signify {{{2 +" let g:signify_sign_add = '▎' +" let g:signify_sign_delete = '▎' +" let g:signify_sign_delete_first_line = '▀' +let g:signify_sign_change = '~' +" let g:signify_sign_changedelete = g:signify_sign_change +" let g:signify_sign_show_text = 0 + +hi SignifySignChange guibg=NONE ctermbg=NONE +hi SignifySignAdd guibg=NONE ctermbg=NONE +hi SignifySignDelete guibg=NONE ctermbg=NONE + +nmap <silent> ]h <Plug>(signify-next-hunk) +nmap <silent> [h <Plug>(signify-prev-hunk) +" }}}2 + +let g:polyglot_disabled = ['markdown', 'org', 'latex', 'tex', 'vue'] +let g:vue_disable_pre_processors = 1 +let g:vim_json_syntax_conceal = 1 + +let g:qs_highlight_on_keys = ['f', 'F', 't', 'T'] " quickscope +let g:utl_cfg_hdl_scm_http = 'silent !xdg-open %u' +let g:sneak#label = 1 + +" vista {{{2 +let g:vista_icon_indent = ['> ', '* '] +let g:vista_default_executive = 'ctags' +" let g:vista_executive_for = { +" \ 'javascript': 'coc', +" \ 'python': 'coc', +" \ } +let g:vista#renderer#enable_icon = 0 +let g:vista#renderer#icons = { +\ 'func': "f", +\ 'function': "f", +\ 'functions': "fs", +\ 'var': "v", +\ 'variable': "v", +\ 'variables': "vs", +\ 'const': "c", +\ 'constant': "c", +\ 'method': "m", +\ 'package': "p", +\ 'packages': "ps", +\ 'enum': "e", +\ 'enumerator': "E", +\ 'module': "m", +\ 'modules': "ms", +\ 'type': "t", +\ 'typedef': "t", +\ 'types': "ts", +\ 'field': "fd", +\ 'fields': "fds", +\ 'macro': "mc", +\ 'macros': "mcs", +\ 'map': "mp", +\ 'class': "cl", +\ 'augroup': "au", +\ 'struct': "st", +\ 'union': "u", +\ 'member': "mb", +\ 'target': "ta", +\ 'property': "pr", +\ 'interface': "if", +\ 'namespace': "ns", +\ 'subroutine': "sr", +\ 'implementation': "im", +\ 'typeParameter': "tp", +\ 'default': "_" +\ } +" }}}2 + +" bufferline {{{2 +let g:bufferline_echo = 0 +let g:bufferline_show_bufnr = 0 +" let g:bufferline_separator = ' ' +let g:bufferline_active_buffer_left = '' +let g:bufferline_active_buffer_right = '' +" let g:bufferline_unamed_buffer = '[no name]' +let g:bufferline_fname_mod = ':t:s?^$?[no name]?' +" }}}2 + +" Enable color highlighting for all filetypes +let g:Hexokinase_ftAutoload = ['*'] +let $SKIM_DEFAULT_COMMAND = '' + +" vim-markdown {{{2 +let g:vim_markdown_folding_disabled = 1 +let g:vim_markdown_math = 1 +let g:vim_markdown_frontmatter = 1 +let g:vim_markdown_json_frontmatter = 1 +let g:vim_markdown_toml_frontmatter = 1 +let g:vim_markdown_folding_level = 2 +let g:vim_markdown_strikethrough = 1 +let g:vim_markdown_auto_insert_bullets = 1 +let g:vim_markdown_new_list_item_indent = 0 +" }}}2 + +" let g:git_messenger_include_diff = v:false +let g:git_messenger_always_into_popup = v:true +let g:git_messenger_close_on_cursor_moved = v:true + +" Neoformat {{{2 +let g:neoformat_basic_format_align = 0 +let g:neoformat_basic_format_retab = 0 +let g:neoformat_basic_format_trim = 0 +let g:neoformat_only_msg_on_error = 1 +" }}}2 + +let g:neoterm_size = 20 +let g:neoterm_autoinsert = 1 + +let g:UltiSnipsExpandTrigger = "<tab>" +let g:UltiSnipsJumpForwardTrigger = "<c-n>" +let g:UltiSnipsJumpBackwardTrigger = "<c-p>" +" }}}1 + +" gui extra config {{{ +" probably obsolete... +if exists('veonim') + " set linespace=5 + set guifont=Iosevka\ R:h17 + runtime init_veonim.vim +endif +" }}} + +function! s:fmt() abort + if exists('g:nofmt') || exists('b:nofmt') + return + endif + try | undojoin | Neoformat | catch /^Vim\%((\a\+)\)\=:E790/ | finally | silent Neoformat | endtry +endfunction + +" autogroups {{{ +augroup SpecialBuffers + au! + " au FileType vista,fzf,skim set laststatus=0 noshowmode noruler + " \| au BufLeave <buffer> set laststatus=2 ruler + au FileType fzf,skim tnoremap <buffer><esc> <c-c> + au FileType gitmessengerpopup nmap <buffer><esc> <Plug>(git-messenger-close) + au FileType vista nmap <buffer><space> :wincmd p<CR> + au FileType help,qf nmap <buffer>q <c-w><c-q> +augroup END + +augroup init + au! + au VimEnter * set iskeyword-=- + " au VimEnter * call vista#RunForNearestMethodOrFunction() + " au VimEnter * Vista!! + + " vim was opened as diff tool, load the 'diff' ftplugin conf + au VimEnter * if &diff | runtime after/ftplugin/diff.vim | endif + + au BufEnter,BufLeave,BufWritePost * rshada|wshada + " au BufWritePre * :call <SID>fmt() + " au FileType dirvish call fugitive#detect(@%) + au BufReadPost * + \ if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit' + \ | exe "normal! g`\"" + \ | endif +augroup END +" }}} + +command! -bang DayMode call daymode#switch(<bang>0) +command! -range Retab <line1>,<line2>call retab#do() +command! NumberToggle call numbertoggle#switch() +command! Alternate call alternate#alternate() +command! -nargs=* Zet call zettel#edit(<f-args>) diff --git a/.vim/init_veonim.vim b/.vim/init_veonim.vim new file mode 100644 index 0000000..7cfa3a6 --- /dev/null +++ b/.vim/init_veonim.vim @@ -0,0 +1,34 @@ +" workspace functions +nno <silent> ,f :Veonim files<cr> +nno <silent> ,e :Veonim explorer<cr> +nno <silent> ,b :Veonim buffers<cr> +nno <silent> ,d :Veonim change-dir<cr> + +" searching text +nno <silent> <space>fw :Veonim grep-word<cr> +vno <silent> <space>fw :Veonim grep-selection<cr> +nno <silent> <space>fa :Veonim grep<cr> +nno <silent> <space>ff :Veonim grep-resume<cr> +nno <silent> <space>fb :Veonim buffer-search<cr> + +" language server functions +nno <silent> sr :Veonim rename<cr> +nno <silent> sd :Veonim definition<cr> +nno <silent> st :Veonim type-definition<cr> +nno <silent> si :Veonim implementation<cr> +nno <silent> sf :Veonim references<cr> +nno <silent> sh :Veonim hover<cr> +nno <silent> sl :Veonim symbols<cr> +nno <silent> so :Veonim workspace-symbols<cr> +nno <silent> sq :Veonim code-action<cr> +nno <silent> sp :Veonim show-problem<cr> +nno <silent> sk :Veonim highlight<cr> +nno <silent> sK :Veonim highlight-clear<cr> +nno <silent> <c-n> :Veonim next-problem<cr> +nno <silent> <c-p> :Veonim prev-problem<cr> +nno <silent> ,n :Veonim next-usage<cr> +nno <silent> ,p :Veonim prev-usage<cr> +nno <silent> <space>pt :Veonim problems-toggle<cr> +nno <silent> <space>pf :Veonim problems-focus<cr> +nno <silent> <d-o> :Veonim buffer-prev<cr> +nno <silent> <d-i> :Veonim buffer-next<cr> diff --git a/.vim/lua/split_term/init.lua b/.vim/lua/split_term/init.lua new file mode 100644 index 0000000..7dcb044 --- /dev/null +++ b/.vim/lua/split_term/init.lua @@ -0,0 +1,46 @@ +local spt = {} + +function spt:create() + self = { + height = 80, + buf_nr = nil, + win_id = nil, + visible = false, + } +end + +function spt:show() + vim.eval('botright new') + if self.buf_nr then + vim.eval('buffer ' .. self.buf_nr) + else + vim.termopen(self.exec, { detach=0 }) + self.buf_nr = vim.bufnr('%') + end +end + +function spt:open() +end + +function spt:hide() +end + +function spt:close() + local force = true + vim.api.nvim_win_close(self.win_id, force) + self.buf_nr = nil + self.win_id = nil +end + +function spt_toggle() + if spt.win_id then + if spt.visible then + spt:hide() + else + spt:show() + end + else + spt:create() + spt:show() + end +end diff --git a/.vim/plug.vim b/.vim/plug.vim new file mode 100644 index 0000000..165f16f --- /dev/null +++ b/.vim/plug.vim @@ -0,0 +1,102 @@ +" get plug.vim if it's not installed +if empty(glob('~/.vim/autoload/plug.vim')) + silent !curl -fLo ~/.vim/autoload/plug.vim --create-dirs + \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim + autocmd VimEnter * PlugInstall --sync | source $MYVIMRC +endif + +call plug#begin('~/.vim/plugged') + +" Core + " Plug 'airblade/vim-gitgutter' " line status next to line nums + Plug 'mhinz/vim-signify' " git status in the sign column + Plug 'tpope/vim-commentary' " auto-comment using `gcc` + Plug 'cohama/lexima.vim' " auto-close parens, brackets, etc. + Plug 'tpope/vim-surround' " edit surroundings in pairs + Plug 'itchyny/lightline.vim' " lightweight status bar + Plug 'neomake/neomake' " async job runner + Plug 'sbdchd/neoformat' " async code formatter + Plug 'justinmk/vim-dirvish' " folder browser + Plug 'justinmk/vim-sneak' " powerful missing vim motions + + " Plug 'neovim/nvim-lsp' + Plug 'SirVer/ultisnips' + \| Plug 'honza/vim-snippets' + + " Plug 'neoclide/coc.nvim', + " \ { 'do': './install.sh nightly' } + " \| Plug 'neoclide/coc-snippets', { 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-json', { 'for': ['json'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-rls', { 'for': ['rust'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-python', { 'for': ['python'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-yaml', { 'for': ['yaml'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-html', { 'for': ['html'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-css', { 'for': ['css'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'josa42/coc-go', { 'for': ['go'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-java', { 'for': ['java'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug 'neoclide/coc-emmet', { 'for': ['html', 'css'], 'do': 'yarn install --frozen-lockfile' } + " \| Plug '~/devel/upstream/github.com/microsoft/vscode-go', { 'for': ['go'] } + + " search + " Plug 'junegunn/fzf', + " \ { 'dir': '~/.fzf', 'do': './install --all'} + " \ |Plug 'junegunn/fzf.vim' + " Plug 'lotabout/skim', + " \ { 'on': 'SK', 'dir': '~/.skim', 'do': './install'} + + Plug 'liuchengxu/vim-clap' " interactive finder / dispatcher + +" Misc + Plug 'junegunn/vim-easy-align', + \ { 'on': ['EasyAlign', '<Plug>(EasyAlign)'] } + Plug 'tpope/vim-sleuth' " auto-set shiftwidth and expandtab options + Plug 'nathanaelkane/vim-indent-guides' " show indentation levels + Plug 'tpope/vim-repeat' " extend '.' to plugins + Plug 'liuchengxu/vista.vim' " sidebar populated by coc, ctags, etc. + Plug 'sgur/vim-editorconfig' " implements editorconfig in vimscript + Plug 'rhysd/conflict-marker.vim' + Plug 'michaeljsmith/vim-indent-object' " adds indentation text-objects + Plug 'kshenoy/vim-signature' " toggle, display and navigate marks + Plug 'RRethy/vim-hexokinase', + \ { 'do': 'make hexokinase' } " highlights color codes + Plug 'rhysd/git-messenger.vim' " git-blame in a popup + Plug 'bling/vim-bufferline' " needed to put buffers into tabline + + Plug 'kassio/neoterm', + \ { 'on': ['T', 'Tnew'] } + +" Languages + " NOTE: these should only be loaded when they are required... + " Plug 'fatih/vim-go', { 'do': ':GoInstallBinaries' } + " Plug 'rhysd/vim-grammarous', { 'for': ['markdown', 'latex', 'tex'] } + Plug 'lervag/vimtex', { 'for': ['latex', 'tex'] } + Plug 'plasticboy/vim-markdown', { 'for': ['markdown'] } + Plug 'masukomi/vim-markdown-folding', { 'for': ['markdown'] } + Plug 'mattn/emmet-vim', { 'for': ['html', 'vue'] } " super-fast HTML editing + Plug 'valloric/matchtagalways', { 'for': ['html', 'vue', 'xml'] } " visually match HTML tags enclosing cursor location + Plug 'ledger/vim-ledger', { 'for': ['ledger'] } + Plug 'jceb/vim-orgmode', { 'for': ['org'] } + \| Plug 'tpope/vim-speeddating' " easy time/date incrementing + \| Plug 'vim-scripts/utl.vim' " follow links/urls + Plug 'gentoo/gentoo-syntax', { 'for': ['ebuild'] } + + " multi-language support with lazy loading + " NOTE: put language specific plugs before this to avoid conflicsts + Plug 'sheerun/vim-polyglot' + +" Minimal UX + Plug 'junegunn/goyo.vim', { 'on': 'Goyo' } " hide ui clutter + Plug 'junegunn/limelight.vim', { 'on': 'Limelight' } " dims colors + " Plug 'equalsraf/neovim-gui-shim' + +" Colorschemes + " Plug 'deviantfero/wpgtk.vim' + " Plug 'KeitaNakamura/neodark.vim' + Plug 'NLKNguyen/papercolor-theme' + " Plug 'crusoexia/vim-dracula' + +" Local + " Plug '~/devel/projects/nihon-theme/vim' + Plug '~/devel/projects/vim-orgdown' + +call plug#end() diff --git a/.vim/plugin/fuzz.vim b/.vim/plugin/fuzz.vim new file mode 100644 index 0000000..0e1cb55 --- /dev/null +++ b/.vim/plugin/fuzz.vim @@ -0,0 +1,16 @@ +" skip this, we have vim-clap now!!! +finish + +if exists('g:loaded_fuzz') + finish +endif +let g:loaded_fuzz = 1 +let g:fuzz_prompt = '❯' + +nmap <silent> ff :call fuzz#files()<CR> +nmap <silent> fb :call fuzz#buffers()<CR> +nmap <silent> fc :call fuzz#commands()<CR> +nmap <silent> fr :Rg<space> + +command! -nargs=* -complete=custom,fuzz#cmdoptions Fuzz :call fuzz#exec(<f-args>) +command! -nargs=* Rg :call fuzz#rg(<f-args>) diff --git a/.vim/plugin/hard.vim b/.vim/plugin/hard.vim new file mode 100644 index 0000000..07a346d --- /dev/null +++ b/.vim/plugin/hard.vim @@ -0,0 +1,65 @@ +" hardmode == not using arrow keys + +function! s:bail() + let l:msg = 'E0X: Use hjkl' + echohl ErrorMsg | echo l:msg | echohl None +endfunction + +function! s:enable_hardmode(bang) + if !a:bang + try + " map arrow keys to more useful actions + " https://coderoncode.com/posts/vim-is-the-perfect-ide + nmap r<up> :resize +2<CR> + nmap r<down> :resize -2<CR> + nmap r<left> :vertical resize +2<CR> + nmap r<right> :vertical resize +2<CR> + + nmap w<up> :wincmd k<CR> + nmap w<down> :wincmd j<CR> + nmap w<left> :wincmd h<CR> + nmap w<right> :wincmd l<CR> + + nmap wm<up> :wincmd K<CR> + nmap wm<down> :wincmd J<CR> + nmap wm<left> :wincmd H<CR> + nmap wm<right> :wincmd L<CR> + + " or bail on them altogether + nmap <up> :call <SID>bail()<CR> + nmap <down> :call <SID>bail()<CR> + nmap <left> :call <SID>bail()<CR> + nmap <right> :call <SID>bail()<CR> + + vmap <up> :call <SID>bail()<CR> + vmap <down> :call <SID>bail()<CR> + vmap <left> :call <SID>bail()<CR> + vmap <right> :call <SID>bail()<CR> + + imap <up> <esc>:call <SID>bail()<CR> + imap <down> <esc>:call <SID>bail()<CR> + imap <left> <esc>:call <SID>bail()<CR> + imap <right> <esc>:call <SID>bail()<CR> + catch + endtry + else + try + nunmap <up> + nunmap <down> + nunmap <left> + nunmap <right> + vunmap <up> + vunmap <down> + vunmap <left> + vunmap <right> + iunmap <up> + iunmap <down> + iunmap <left> + iunmap <right> + catch + endtry + endif +endfunction + +call s:enable_hardmode(0) +command! -bang Hardmode call s:enable_hardmode(<bang>0) diff --git a/.vim/plugin/line.vim b/.vim/plugin/line.vim new file mode 100644 index 0000000..66072c3 --- /dev/null +++ b/.vim/plugin/line.vim @@ -0,0 +1,159 @@ +let g:lightline = {} +let g:lightline.colorscheme = 'papercolor_dim' + +let g:lightline.enable = { 'statusline': 1, 'tabline': 1 } + +let g:lightline.mode_map = { +\ 'n' : 'N', +\ 'i' : 'I', +\ 'R' : 'R', +\ 'v' : 'V', +\ 'V' : 'V-LINE', +\ "\<C-v>": 'V-BLOCK', +\ 'c' : '>', +\ 's' : 'SELECT', +\ 'S' : 'S-LINE', +\ "\<C-s>": 'S-BLOCK', +\ 't': 'T', +\ } + +let g:lightline.active = { +\ 'left': [ +\ ['mode', 'paste'], +\ ['filename', 'vista'], +\ ['neomake', 'coc', 'ctags', 'obsession'], +\ ], +\ 'right': [ +\ ['filetype', 'fileencoding', 'fileformat'], +\ ['lineinfo', 'percent'], +\ ['tabstatus'], +\ ], +\ } + +set showtabline=2 +let g:lightline.tabline = { +\ 'right': [ +\ ['tabs'], +\ ['porcelain'], +\ ], +\ } + +let g:lightline.tab = { +\ 'active': ['tabnum'], +\ 'inactive': ['tabnum'], +\ } + +let g:lightline.component = { +\ 'lineinfo': ' %l:%v', +\ 'filetype': "%{winwidth(0) > 70 ? (strlen(&filetype) ? &filetype : 'no ft') : ''}", +\ 'dir': "%{substitute(expand('%:p:h'), $HOME, '~', '')}", +\ } + +let g:lightline.component_function = { +\ 'filename': 'LightLineFilename', +\ 'fileformat': 'LightLineFileformat', +\ 'fileencoding': 'LightLineFileencoding', +\ 'mode': 'LightLineMode', +\ 'vista': 'LightLineVista', +\ 'o': 'LightLineO', +\ 'porcelain': 'LightLinePorcelain', +\ } + +" \ 'separator': { 'left': "\ue0b8", 'right': "\ue0be" }, +" \ 'subseparator': { 'left': "\ue0b9", 'right': "\ue0b9" }, +" \ 'tabline_separator': { 'left': "\ue0bc", 'right': "\ue0ba" }, +" \ 'tabline_subseparator': { 'left': "\ue0bb", 'right': "\ue0bb" }, +" \ + +let g:lightline.component_expand = {} +let g:lightline.component_type = {} + + +" Modified {{{ +hi ModifiedColor ctermfg=196 guifg=#ff0000 gui=bold +function! LightLineModified() + return &modified ? '+' : &modifiable ? '' : '-' +endfunction +" }}} + +" Readonly {{{ +function! LightLineReadonly() + return &filetype !~? 'help\|vimfiler\|gundo' && &readonly ? '' : '' +endfunction +" }}} + +" Filename {{{ +function! LightLineFilename() + return ('' !=? LightLineReadonly() ? LightLineReadonly() . ' ' : '') . + \ (&filetype ==# 'vimfiler' ? vimfiler#get_status_string() : + \ &filetype ==# 'unite' ? unite#get_status_string() : + \ &filetype ==# 'vimshell' ? vimshell#get_status_string() : + \ '' !=? expand('%:t') ? expand('%:t') : '[No Name]') . + \ ('' !=? LightLineModified() ? ' ' . LightLineModified() : '') +endfunction +" }}} + +" Fileformat {{{ +function! LightLineFileformat() + if (&fileformat == 'unix') + return '' + endif + return winwidth(0) > 70 ? &fileformat : '' +endfunction +" }}} + +" Fileencoding {{{ +function! LightLineFileencoding() + if (&fileencoding == 'utf-8') + return '' + endif + return winwidth(0) > 70 ? (strlen(&fileencoding) ? &fileencoding : &encoding) : '' +endfunction +" }}} + +" Mode {{{ +function! LightLineMode() + return winwidth(0) > 60 ? ( + \ expand('%:t') ==# '__Tagbar__' ? 'Tagbar': + \ &filetype ==# 'vimfiler' ? 'VimFiler' : + \ &filetype ==# 'vimshell' ? 'VimShell' : + \ &filetype ==# 'dirvish' ? 'DIRVISH' : + \ lightline#mode() ) + \ : '' +endfunction +" }}} + +" Tabchar {{{ +function! LightLineTabchar() + return '[' + \ . &tabstop + \ . (&expandtab == 1 ? '->' : '<>') + \ . &shiftwidth + \ . ']' +endfunction +" }}} + +" Vista {{{ +function! LightLineVista() + let l:sym = get(b:, 'vista_nearest_method_or_function', '') + return empty(l:sym) ? '' : g:vista#renderer#icons.function . ' ' . l:sym +endfunction +" }}} + +" Porcelain {{{ +function! LightLinePorcelain() + if &buftype !=# 'terminal' + let gitinfo = system('porcelain -no-color -path=' . expand('%:h')) + return (v:shell_error ? '' : gitinfo) + endif +endfunction +" }}} + +function! LightLineO() + let l:circ_empty="\u25CB" + let l:circ_quart="\u25D4" + let l:circ_half="\u25D1" + let l:circ_threequart="\u25D5" + let l:circ_full="\u25CF" + return ('' != LightLineModified() ? l:circ_full : l:circ_empty) +endfunction diff --git a/.vim/plugin/split_term.vim b/.vim/plugin/split_term.vim new file mode 100644 index 0000000..efb9b86 --- /dev/null +++ b/.vim/plugin/split_term.vim @@ -0,0 +1,11 @@ +" SplitTerm: quickly pop out a terminal + +" skip this while were testing kassio/neoterm +finish + +" lua require('split_term') +" command! LuaTerm :lua spt_toggle()<CR> + +command! -bang -nargs=* SplitTerm call s:split_term_toggle(<bang>0, <f-args>) +cnoreabbrev spt SplitTerm +cnoreabbrev st SplitTerm diff --git a/.vim/syntax/gotmpl.vim b/.vim/syntax/gotmpl.vim new file mode 100644 index 0000000..9e6c1e1 --- /dev/null +++ b/.vim/syntax/gotmpl.vim @@ -0,0 +1,93 @@ +" Copyright 2011 The Go Authors. All rights reserved. +" Use of this source code is governed by a BSD-style +" license that can be found in the LICENSE file. +" +" gotexttmpl.vim: Vim syntax file for Go templates. + +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +if !exists("g:main_syntax") + let g:main_syntax = 'html' +endif + +runtime! syntax/html.vim + +syn case match + +" Go escapes +syn match goEscapeOctal display contained "\\[0-7]\{3}" +syn match goEscapeC display contained +\\[abfnrtv\\'"]+ +syn match goEscapeX display contained "\\x\x\{2}" +syn match goEscapeU display contained "\\u\x\{4}" +syn match goEscapeBigU display contained "\\U\x\{8}" +syn match goEscapeError display contained +\\[^0-7xuUabfnrtv\\'"]+ + +hi def link goEscapeOctal goSpecialString +hi def link goEscapeC goSpecialString +hi def link goEscapeX goSpecialString +hi def link goEscapeU goSpecialString +hi def link goEscapeBigU goSpecialString +hi def link goSpecialString Special +hi def link goEscapeError Error + +" Strings and their contents +syn cluster goStringGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU,goEscapeError +syn region goString contained start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@goStringGroup +syn region goRawString contained start=+`+ end=+`+ + +hi def link goString String +hi def link goRawString String + +" Characters; their contents +syn cluster goCharacterGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU +syn region goCharacter contained start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=@goCharacterGroup + +hi def link goCharacter Character + +" Integers +syn match goDecimalInt contained "\<\d\+\([Ee]\d\+\)\?\>" +syn match goHexadecimalInt contained "\<0x\x\+\>" +syn match goOctalInt contained "\<0\o\+\>" +syn match goOctalError contained "\<0\o*[89]\d*\>" +syn cluster goInt contains=goDecimalInt,goHexadecimalInt,goOctalInt +" Floating point +syn match goFloat contained "\<\d\+\.\d*\([Ee][-+]\d\+\)\?\>" +syn match goFloat contained "\<\.\d\+\([Ee][-+]\d\+\)\?\>" +syn match goFloat contained "\<\d\+[Ee][-+]\d\+\>" +" Imaginary literals +syn match goImaginary contained "\<\d\+i\>" +syn match goImaginary contained "\<\d\+\.\d*\([Ee][-+]\d\+\)\?i\>" +syn match goImaginary contained "\<\.\d\+\([Ee][-+]\d\+\)\?i\>" +syn match goImaginary contained "\<\d\+[Ee][-+]\d\+i\>" + +hi def link goInt Number +hi def link goFloat Number +hi def link goImaginary Number + +" Token groups +syn cluster gotplLiteral contains=goString,goRawString,goCharacter,@goInt,goFloat,goImaginary +syn keyword gotplControl contained if else end range with template +syn keyword gotplFunctions contained and html index js len not or print printf println urlquery eq ne lt le gt ge +syn match gotplVariable contained /\$[a-zA-Z0-9_]*\>/ +syn match goTplIdentifier contained /\.[^\s}]+\>/ + +hi def link gotplControl Keyword +hi def link gotplFunctions Function +hi def link goTplVariable Special + +syn region gotplAction start="{{" end="}}" contains=@gotplLiteral,gotplControl,gotplFunctions,gotplVariable,goTplIdentifier display +syn region gotplAction start="\[\[" end="\]\]" contains=@gotplLiteral,gotplControl,gotplFunctions,gotplVariable display +syn region goTplComment start="{{\(- \)\?/\*" end="\*/\( -\)\?}}" display +syn region goTplComment start="\[\[\(- \)\?/\*" end="\*/\( -\)\?\]\]" display + +hi def link gotplAction PreProc +hi def link goTplComment Comment + +syn cluster htmlPreproc add=gotplAction,goTplComment + +let b:current_syntax = "gotmpl" + +" vim: sw=2 ts=2 et diff --git a/bin/askpass b/bin/askpass new file mode 100755 index 0000000..fe02ef4 --- /dev/null +++ b/bin/askpass @@ -0,0 +1,8 @@ +#!/bin/bash + +echo "${ASKPASS_PROMPT:-Enter your password}" | \ + wofi -k /dev/null \ + --height 50 \ + --dmenu \ + --password \ + --prompt ' Password' diff --git a/bin/chrome-extension-dl b/bin/chrome-extension-dl new file mode 100755 index 0000000..a40b519 --- /dev/null +++ b/bin/chrome-extension-dl @@ -0,0 +1,83 @@ +#!/usr/bin/env sh +# Download extensions from chrome webstore +# +# source: https://gitlab.com/snippets/1888145 + +[ -z "$EXTENSIONS_DIR" ] && EXTENSIONS_DIR="${extensions_dir:=${XDG_DATA_HOME:=$HOME/.local/share}/chrome-extensions}" + +magenta="\033[35;1m" +nc="\033[m" + +die() { printf 'Error: %s\n' "$1" >&2; exit 1; } + +parse_url(){ + extension_id="$(echo "$1" | grep -oP '[_a-zA-Z0-9]{30,}')" + download_extension +} + +download_extension(){ + extension_file="$(mktemp "${TMPDIR:-/tmp/}$extension_id.XXXXXXXXXXXX.zip")" + printf "\nDownloading extension %s\n" "$extension_id" + curl -fsSL "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=49.0&x=id%3D$extension_id%26installsource%3Dondemand%26uc" --output "$extension_file" || die "Unable to download extension" + extract_extension +} + +extract_extension(){ + nohup rm -fr "$extensions_dir/${extension_id:?}" > /dev/null 2>&1 ||: + mkdir -p "$extensions_dir/$extension_id" + nohup unzip -q "$extension_file" -d "$extensions_dir/$extension_id" > /dev/null 2>&1 + printf "\n Extension installed to directory %s\n" "$extensions_dir/$extension_id" + install_help +} + +update_extensions(){ + cd "$extensions_dir" || die "Extensions directory does not exist" + for extension in *; do + if [ -d "$extension" ]; then + printf "\nUpdating extension %s\n" "$extension" + extension_id="$extension" + download_extension + fi + done +} + +general_help(){ + echo -e " + ${magenta}NAME${nc} + $(basename "$0") - install chrome extensions + + ${magenta}SYNOPSIS${nc} + $(basename "$0") [webstore url] + $(basename "$0") update + + ${magenta}DESCRIPTION${nc} + $(basename "$0") Download extensions from chrome webstore. + + set EXTENSIONS_DIR to change extensions folder." +} + +install_help(){ + echo -e " + ${magenta}Install Guide:${nc} + 1. Visit chrome://extensions in your browser. + 2. Ensure that the Developer mode checkbox in the top right-hand corner is checked. + 3. Click Load unpacked extension... to pop up a file-selection dialog. + 4. Navigate to $extensions_dir/, and select it." +} + +main(){ + if [ "$#" -eq 0 ]; then + general_help && install_help + else + for input in "$@"; do + shift + case "$input" in + -h|--h*) general_help && install_help ;; + u*) update_extensions ;; + *) parse_url "$input" ;; + esac + done + fi +} + +main "$@" diff --git a/bin/clone b/bin/clone new file mode 100755 index 0000000..1882a25 --- /dev/null +++ b/bin/clone @@ -0,0 +1,127 @@ +#!/bin/bash +# +# To use this create a directory that you want to use for git repos, e.g. $HOME/git and copy/symlink this script into it. +# Then you can use it like this: +# +# ./clone github.com/robertgzr/dotfiles +# ./clone https://bitbucket.org/foo/bar +# +# If you don't have gitconfig set up like I have (using aliases for github/bitbucket ssh access) this won't really work +set -e + +SHALLOW=0 +RECURSE_SUBMODULES= +TEMP="$(mktemp -d -p /tmp clone.XXXXXXXXXX)" + +URL=$1 +if [[ -z "${URL}" ]]; then + fail "no input" +else + URL="${URL//(http|https):\/\//}" +fi + +make_dirs() { + local url=$1 + + if [[ -d "${url}" ]] + then + echo "exists: ${url}" + return + fi + + confirm "create ${url}" + mkdir -p "${url}" +} + +clone() { + local in=$1 + local to=$2 + IFS='/' read -r -a url <<< "${in}" + + case "${url[0]}" in + github.com ) ssh_clone gh "${in}" "${to}";; + gitlab.com ) ssh_clone gl "${in}" "${to}";; + bitbucket.org ) ssh_clone bb "${in}" "${to}";; + git.sr.ht ) ssh_clone srht "${in}" "${to}";; + * ) http_clone "${url[@]// /\/}" "${to}";; + esac +} + +ssh_clone() { + local prefix=$1 + local in=$2 + local to=$3 + IFS='/' read -r -a url <<< "${in}" + local ssh_url="${prefix}:${url[1]}/${url[2]}" + + # confirm "clone $ssh_url" + git_clone "${ssh_url}" "${to}" +} + +http_clone() { + local to=$2 + local http_url="https://${URL}" + + # confirm "clone $http_url" + git_clone "${GIT_FLAGS}" "${http_url}" "${to}" +} + +git_clone() { + declare -a GIT_FLAGS + if [[ ${SHALLOW} -ne 0 ]]; then + GIT_FLAGS+="--depth=${SHALLOW}" + fi + if [[ ! -z ${RECURSE_SUBMODULES} ]]; then + GIT_FLAGS+="--recurse-submodules" + fi + git clone "${GIT_FLAGS[@]}" "${@}" +} + +fail() { + echo "${1:-error}" + exit ${2:-1} +} + +confirm() { + echo -n "${1:-'do something'}, ok (y/n)? " + read yn + if [[ "$yn" = "${yn#[Yy]}" ]]; then + fail "ok, exiting" 0 + fi +} + +usage() { + echo "usage: clone [-s][-r] URL" + echo "" + echo -e " -s\tshallow clone" + echo -e " -r\trecurse submodules" + echo -e " URL\texample.com/user/repo" + echo -e "\thttp[s]://example.com/user/repo" + exit 0 +} + +while getopts ":hsr" opt; do + case ${opt} in + s ) + echo "Shallow clone" + SHALLOW=1 + ;; + r ) + echo "Recurse submodules" + RECURSE_SUBMODULES=1 + ;; + h ) + usage + ;; + \? ) + usage + ;; + esac +done +shift $((OPTIND -1)) + +clone "${URL}" "${TEMP}/${URL}" || fail + +target="${PWD}/$(dirname "${URL}")" +install -d "${target}" || fail +mv "${TEMP}/${URL}" "${target}" || fail diff --git a/bin/colorpick b/bin/colorpick new file mode 100755 index 0000000..bb099cd --- /dev/null +++ b/bin/colorpick @@ -0,0 +1,6 @@ +#!/bin/sh +# dependencies: grim slurp wl-clipboard +# src: https://git.sr.ht/~sircmpwn/dotfiles/tree/f9e7e88a72c2b87e5ab823205cb3a3aa8f3babd0/bin/colorpick +color=$(grim -g "$(slurp -p -b 00000000)" - | convert - txt:- | awk 'END{print $3}') +wl-copy "$color" +notify-send "Color copied to clipboard" "$color" --icon '/usr/share/icons/Adwaita/scalable/actions/color-select-symbolic.svg' diff --git a/bin/find-rust-target-dir b/bin/find-rust-target-dir new file mode 100755 index 0000000..b59a2b4 --- /dev/null +++ b/bin/find-rust-target-dir @@ -0,0 +1,16 @@ +#!/bin/bash + +is_rust_root() { + [ -f $1/Cargo.toml ] || return 1 + [ -d $1/target ] || return 1 + return 0 +} + +inspect() { + for dir in `fd -t d -d 1 '' "${1}"`; do + is_rust_root ${dir} && { echo ${dir}; du -h -d 0; echo -e "\n"; continue; } + inspect ${dir} + done +} + +inspect ${1:-$PWD} diff --git a/bin/gh b/bin/gh new file mode 100755 index 0000000..66b7767 --- /dev/null +++ b/bin/gh @@ -0,0 +1,114 @@ +#!/bin/bash + +set -e -o pipefail + +CURL='curl -sSL' +JQ='jq -r' + +GH_ACCESS_TOKEN=${GH_ACCESS_TOKEN:-`cat $DOT_DIR/GH_TOKEN_GH_SCRIPT`} + +usage() { + printf "usage: gh [-v] [commands] [options]\n" + printf "\ncommands:\n" + printf " release-latest REPO\t\t prints the latest version tag of REPO\n" + printf " release-url REPO TAG\t prints the url to the release TAG of REPO\n" + printf " tag-latest REPO\t\t prints the latest version tag of REPO\n" + printf " gql QUERY\t\t sends a query to the v4 graphql API\n" + printf "\n" +} + +err() { + echo >&2 "error: $@" +} + +has_cmd() { + if ! command -v $1 1,2&>/dev/null; then + err "$s not found" + return 1 + fi +} + +gql() { + local q=$1 + local res=`$CURL https://api.github.com/graphql \ + -X POST \ + -H "Authorization: bearer $GH_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"query\":\"query{ $q }\"}"` + + [[ $? -ne 0 ]] && { + err `echo $res | $JQ '.message'` + err "request was" "{\"query\":\"query{ $q }\"}" + exit 1 + } + [[ -z "$res" ]] && { + err "empty response" + exit 1 + } + local errors=`echo $res | $JQ '.errors?'` + [[ $errors -ne "null" ]] && { + err "$errors" + err "request was" "{\"query\":\"query{ $q }\"}" + exit 1 + } + echo $res +} + +if [ -z "$GH_ACCESS_TOKEN" ]; then + err "no github access token" + exit 1 +fi + +has_cmd jq + +cmd="$1"; opt="$2" +case $cmd in + + gql) + if [[ -z $opt ]]; then + err "no QUERY found" + exit 1 + fi + gql "$opt" | jq '.' + exit 0 + ;; + + release-latest) + if [[ -z $opt ]]; then + err "no REPO found" + exit 1 + fi + opt=(${opt//\// }) + gql "repository(owner:\\\"${opt[0]}\\\",name:\\\"${opt[1]}\\\"){ releases(first:1, orderBy:{field:CREATED_AT, direction:DESC}){ nodes{tag{name}}} }" | $JQ '.data.repository.releases.nodes[].tag.name' + exit 0 + ;; + + tag-latest) + if [[ -z $opt ]]; then + err "no REPO found" + exit 1 + fi + opt=(${opt//\// }) + gql "repository(owner:\\\"${opt[0]}\\\",name:\\\"${opt[1]}\\\"){ refs(first: 1, orderBy:{field:TAG_COMMIT_DATE, direction:DESC}, refPrefix:\\\"refs/tags/\\\"){ nodes{name}} }" | $JQ '.data.repository.refs.nodes[].name' + exit 0 + ;; + + + release-url) + if [[ -z $opt ]]; then + err "no REPO found" + exit 1 + fi + if [[ -z $3 ]]; then + err "no TAG found" + fi + opt=(${opt//\// }) + gql "repository(owner:\\\"${opt[0]}\\\",name:\\\"${opt[1]}\\\"){release(tagName:\\\"$3\\\"){url}}" | $JQ '.data.repository.release.url' + exit 0 + ;; + + *) + usage + exit 0 + ;; +esac diff --git a/bin/grimshot b/bin/grimshot new file mode 100755 index 0000000..819e7c5 --- /dev/null +++ b/bin/grimshot @@ -0,0 +1,146 @@ +#!/bin/sh + +## Grimshot: a helper for screenshots within sway +## Requirements: +## - `grim`: screenshot utility for wayland +## - `slurp`: to select an area +## - `wf-recorder`: screen-recording utility +## - `swaymsg`: to read properties of current window +## - `wl-copy`: clipboard utility +## - `jq`: json uliity to parse swaymsg output +## - `notify-send`: to show notifications +## - `mktemp`: to create a temporary file +## Those are needed to be installed, if unsure, run `grimshot check` +## +## Examples: +## `grimshot copy win` - to copy current window +## `grimshot save area` - to select area and save it to default file (Pictures/Grimshot-$datetime.png) +## `grimshot save screen ~/screenshot.png` - to save screenshot under ~/screenshot.png +## `grimshot | grimshot -h` - usage +## `grimshot check` - verify if tools are installed + +ACTION=${1:-usage} +SUBJECT=${2:-screen} +FILE=${3:-$(xdg-user-dir PICTURES)/screenshots/$(now).png} +case "$ACTION" in + usage|help|-h|--help) + echo "Usage:" + echo " grimshot copy|save|rec|rec-copy win|screen|area [FILE]" + echo "Troubleshoot:" + echo " grimshot check" + exit +esac + +notify() { + # notify-send -i "${TMP:-${FILE}}" -t 3000 -a grimshot "$@" + notify-send -i "${TMP:-${FILE}}" -t 3000 "$@" +} +notifyOk() { + TITLE=${2:-"Screenshot"} + MESSAGE=${1:-"OK"} + notify "$TITLE" "$MESSAGE" +} +notifyError() { + TITLE=${2:-"Screenshot"} + MESSAGE=${1:-"Error taking screenshot with grim"} + notify -u critical "$TITLE" "$MESSAGE" +} + +die() { + MSG=${1:-Bye} + notifyError "Error: $MSG" + exit 2 +} + +check() { + COMMAND=$1 + command -v "$COMMAND" > /dev/null 2>&1 + if [ $? ]; then + RESULT="OK" + else + RESULT="NOT FOUND" + fi + echo " $COMMAND: $RESULT" +} + +takeScreenshot() { + FILE=$1 + GEOM=$2 + if [ -z "$GEOM" ]; then + grim "$FILE" || die "Unable to invoke grim" + else + grim -g "$GEOM" "$FILE" || die "Unable to invoke grim" + fi +} + +recordScreen() { + FILE=$1 + GEOM=$2 + if [ -z "$GEOM" ]; then + wf-recorder -f "$FILE" || die "Unable to invoke wf-recorder" + else + wf-recorder -g "$GEOM" -f "$FILE" || die "Unable to invoke wf-recorder" + fi +} + +if [ "$ACTION" = "check" ] ; then + echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..." + check grim + check slurp + check wf-recorder + check swaymsg + check wl-copy + check jq + check notify-send + check mktemp + exit +elif [ "$SUBJECT" = "area" ] ; then + GEOM=$(slurp -d) + WHAT="Area" +elif [ "$SUBJECT" = "win" ] ; then + FOCUSED=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?, .floating_nodes[]?) | select(.focused)') + GEOM=$(echo "$FOCUSED" | jq -r '.rect | "\(.x),\(.y) \(.width)x\(.height)"') + APP_ID=$(echo "$FOCUSED" | jq -r '.app_id') + WHAT="$APP_ID window" +elif [ "$SUBJECT" = "screen" ] ; then + GEOM="" + WHAT="Screen" +else + die "Unknown subject to take a screen shot from" "$SUBJECT" +fi + +if [ "$ACTION" = "copy" ] ; then + TMP=$(mktemp) || die "Unable to create temp file: is mktemp installed?" + takeScreenshot "$TMP" "$GEOM" + wl-copy --type image/png < "$TMP" || die "Clipboard error" + notifyOk "$WHAT copied to buffer" + rm "$TMP" +elif [ "$ACTION" = "save" ] ; then + takeScreenshot "$FILE" "$GEOM" + if [ $? ]; then + TITLE="Screenshot of $SUBJECT" + MESSAGE=$(basename "$FILE") + notifyOk "$MESSAGE" "$TITLE" + else + notifyError "Error taking screenshot with grim" + fi +elif [ "$ACTION" = "rec" ] ; then + echo "$FILE" | awk '/png$/{exit 0}' && \ + FILE=$(xdg-user-dir VIDEOS)/grimshot/$(now).mp4 + recordScreen "$FILE" "$GEOM" + if [ $? ]; then + TITLE="Screen recording of $SUBJECT" + MESSAGE=$(basename "$FILE") + notifyOk "$MESSAGE" "$TITLE" + else + notifyError "Error recording screen with wf-recorder" + fi +elif [ "$ACTION" = "rec-copy" ] ; then + TMP=$(mktemp --suffix '.mp4') || die "Unable to create temp file: is mktemp installed?" + recordScreen "$TMP" "$GEOM" + wl-copy --type video/mp4 < "$TMP" || die "Clipboard error" + notifyOk "$WHAT copied to buffer" + rm "$TMP" +else + die "Unknown action" "$ACTION" +fi diff --git a/bin/hibiki/mount-solus-backup b/bin/hibiki/mount-solus-backup new file mode 100755 index 0000000..7aaf44b --- /dev/null +++ b/bin/hibiki/mount-solus-backup @@ -0,0 +1,35 @@ +#!/bin/env bash + +D_SQUASHFS="solus_2019-12-23.squashfs" +D_RAW="${D_SQUASHFS%.squashfs}.img" + +mount() { + udisksctl mount -b /dev/disk/by-partlabel/rTranscend + + mkdir -p /tmp/mnt-solus-squashfs + sudo mount /run/media/robert/rTranscend/${D_SQUASHFS} /tmp/mnt-solus-squashfs + + sudo losetup --partscan /dev/loop1 /tmp/mnt-solus-squashfs/${D_RAW} + sudo pvs + sudo vgchange -a y SolusSystem + + mkdir -p /tmp/mnt-solus + sudo mount -o ro /dev/SolusSystem/Root /tmp/mnt-solus + + ls /tmp/mnt-solus +} + +unmount() { + sudo umount /tmp/mnt-solus + sudo vgchange -a n SolusSystem + sudo losetup -d /dev/loop1 + sudo umount /tmp/mnt-solus-squashfs + udisksctl unmount -b /dev/disk/by-partlabel/rTranscend +} + +set -x + +case "$1" in + -u) unmount ;; + *) mount ;; +esac diff --git a/bin/hibiki/soundswitch b/bin/hibiki/soundswitch new file mode 100755 index 0000000..9ba73c9 --- /dev/null +++ b/bin/hibiki/soundswitch @@ -0,0 +1,41 @@ +#!/bin/sh +set -ex + +global= +focused= +profile= + +while getopts fgp: name; do + case $name in + g) global=1 ;; + f) focused=1 ;; + p) profile=1 ;; + ?) printf "usage: %s [-gf]\n" "$0" >&2; exit 2 ;; + esac +done + +if [ "$#" -lt 1 ]; then + printf "global\nfocused\nprofile\n" | menu | { + read -r line + case "${line}" in + global) exec "$0" -g ;; + focused) exec "$0" -f ;; + profile) exec "$0" -p ;; + esac + } +fi + +if [ -n "${global}" ]; then + pactl list short sinks | awk '{print $2}' | menu | xargs pactl set-default-sink + exit 0 +fi + +if [ -n "${focused}" ]; then + printf "not implemented\n" >&2 + exit 1 +fi + +if [ -n "${profile}" ]; then + printf "not implemented\n" >&2 + exit 1 +fi diff --git a/bin/hr b/bin/hr new file mode 100755 index 0000000..9c421fc --- /dev/null +++ b/bin/hr @@ -0,0 +1,20 @@ +#!/bin/sh + +str="$1" +[[ -n $str ]] && str=" $str " +getcols() { + if command -v tput &>/dev/null; then + tput cols + else + stty size | awk '{print $2}' + fi +} +COLS=$((${COLUMNS:-`getcols`} - ${#str} - 2)) +start=$'\e(0'; end=$'\e(B'; line="qqqqqqqqqqqqqqqq" +while ((${#line}<$COLS)); do line+=$line; done +printf "╾" # └ +printf "%s%s%s" "$start" "${line:0:$((COLS/2))}" "$end" +printf "%s" "$str" +printf "%s%s%s" "$start" "${line:0:$((COLS/2))}" "$end" +printf "╼" # ┐ +printf "\n" diff --git a/bin/kp b/bin/kp new file mode 100755 index 0000000..f578b04 --- /dev/null +++ b/bin/kp @@ -0,0 +1,75 @@ +#!/bin/sh + +set -e -o pipefail + +KP="$HOME/.local/bin/KeePassXC-2.5.1-x86_64.AppImage" +[ -e "$KP" ] || { + echo "$KP not found" >&2; exit 1 +} + +DB="${KP_DB:-$HOME/Dropbox/keepass-box.kdbx}" +[ -e "$DB" ] || { + echo "$DB not found" >&2; exit 1 +} + +keepass() +{ + "$KP" $@ +} + +wofi() +{ + wofi -k /dev/null --dmenu --prompt 'Select an entry' +} + +unset passwd +passwd="$(ASKPASS_PROMPT='Enter KeePassXC password' askpass)" +[ -n "$passwd" ] || { + echo "No password" >&2; exit 1 +} + +_kp_command=show +_kp_opts='-q -a Password' +_kp_extra_args= +case $1 in + clip) + _kp_command='clip' + _kp_opts='-q' + _kp_extra_args='15' # timeout + shift + ;; + dump) + echo "$passwd" | keepass cli export -f csv "$DB" + exit 0 + ;; +esac + +entry="$1" +[ -n "$entry" ] || { + entry=$(echo "$passwd" \ + | keepass cli ls -q -R "$DB" \ + | awk ' +{ + if ($0~/\/$/) { + sub(/^\s+/, "") + if ($0!~/^\s+/) { + grp = $0 + } else { + print substr($0, match($0, /^\s+/)) + grp = grp $0 + } + next + } else { + sub(/^\s+/, "") + print "/" grp $0 + } +}' \ + | grep -v '^/Recycle Bin' \ + | sort -t/ -k2n \ + | wofi) + [ -n "$entry" ] || { + echo "No entry" >&2; exit 1 + } +} + +echo "$passwd" | keepass cli "$_kp_command" "$_kp_opts" "$DB" "$entry" "$_kp_extra_args" diff --git a/bin/lock b/bin/lock new file mode 100755 index 0000000..b147765 --- /dev/null +++ b/bin/lock @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +lock_bg=$HOME/.background_lock + +blur() { + if [ -n "$USE_GRIM" ]; then + in=$(mktemp --tmpdir lock-XXX.png) + grim -o $(swq current output) ${in} + else + in=$(readlink -f $HOME/.background) + fi + + convert ${in} -filter Gaussian -resize 50% \ + -define filter:sigma=8.0 -resize 200% ${lock_bg} + # convert ${in} -scale 5% -blur 0x2.5 -resize 2000% ${lock_bg} + # $GOPATH/bin/stackblur -radius 60 -in $img -out $img + # $HOME/devel/go/bin/stackblur -radius 60 -in ${in} -out ${lock_bg} >/dev/null +} + +case "$1" in + -h) echo "usage: $0 [gen]"; exit 0 ;; + gen) blur; exit 0 ;; + *) exec swaylock -f -i ${lock_bg} \ + --scaling=fill \ + --font=Iosevka\ R\ Medium \ + --ignore-empty-password \ + --indicator-caps-lock \ + --show-failed-attempts \ + --hide-keyboard-layout ;; +esac diff --git a/bin/lowbat b/bin/lowbat new file mode 100755 index 0000000..90702a1 --- /dev/null +++ b/bin/lowbat @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +acpi -b | awk -F'[,:%]' '{print $2, $3}' | { + read -r status capacity + + if [[ $status -eq "Discharging" ]]; then + if [[ $capacity -lt 10 ]]; then + echo "battery capacity: $capacity" + notify-send "battery low" "System will hibernate at 5%" + fi + if [[ $capacity -lt 5 ]]; then + echo "hibernating" + notify-send "battery low" "System will hibernate now" + systemctl hibernate + fi + fi +} diff --git a/bin/mega b/bin/mega new file mode 100755 index 0000000..b59ebe5 --- /dev/null +++ b/bin/mega @@ -0,0 +1,33 @@ +#!/bin/bash +# +# little convenience wrapper around the mega cli tools +# + +usage() { + echo "usage: mega -u USER -p PASSWD COMMAND" + echo "" + exit 0 +} + +while getopts ":hu:p:" o; do + case $o in + u) + user=${OPTARG} ;; + p) + passwd=${OPTARG} ;; + h | *) + usage ;; + esac +done +shift $((OPTIND - 1)) + +[ -z "$1" ] || [ -z "$user" ] || [ -z "$passwd" ] && usage +if ! command -v ${1} &>/dev/null; then + echo "error: ${1} not installed." >&2 + exit 1 +else + cmd=$1 +fi +shift + +exec "mega${cmd}" -u "${user}" -p "${passwd}" $@ diff --git a/bin/menu b/bin/menu new file mode 100755 index 0000000..b415d1a --- /dev/null +++ b/bin/menu @@ -0,0 +1,28 @@ +#!/bin/sh + +arg0=bemenu +while test $# -gt 0; do + case "$1" in + -run) + arg0=bemenu-run; prompt=bin ;; + -p) + shift; prompt="$1" ;; + esac + shift +done + +exec "${arg0}" \ + -i \ + --bottom \ + --no-overlap \ + --prompt "${prompt}:" \ + --fn 'Iosevka Sparkle 11' \ + --nb '#221E1D' --nf '#fffefe' \ + --tb '#0B346F' --tf '#fffefe' \ + --fb '#221E1D' --ff '#fffefe' \ + --sb '#FAD689' \ + --sf '#221E1D' \ + --hb '#0B346F' \ + --hf '#FCFAF2' \ + --scb '#221E1D' --scf '#fffefe' \ + "$@" diff --git a/bin/mesoninit b/bin/mesoninit new file mode 100755 index 0000000..5ce3b79 --- /dev/null +++ b/bin/mesoninit @@ -0,0 +1,191 @@ +#!/bin/bash +# vim: ft=sh + +_meson_dir=`pwd` +_meson_lang=c + +while getopts ":hl:" o; do + case $o in + l) + _meson_lang=$OPTARG + ;; + h | *) + echo "usage: $0 [flags]" + echo "" + echo "-h :: print help" + echo "-l LANG (default: c) :: language to use" + echo "" + exit 0 + ;; + esac +done +shift $((OPTIND - 1)) + +log() { + echo "[*] ${@}" >&2 +} + +# allow setting target dir +if [[ -n $1 ]]; then + _meson_dir="$1" + [[ ! -d $_meson_dir ]] && mkdir -p $_meson_dir + log "creating in $_meson_dir" +fi + +_meson_bin=`basename $_meson_dir` +_meson_ignore_extra= +declare -a _meson_default_options +declare -a _meson_default_files + +case $_meson_lang in + + ### C ### + c) + _meson_default_options=( \ + 'c_std=c11' \ + 'warning_level=2' \ + 'werror=true' \ + ) + _meson_default_files=('src/main.c') + _meson_ignore_extra='**/*.o' + _meson_output=" +x = executable( + '$_meson_bin', + sources: src +)" + + log "writing main.c" + mkdir $_meson_dir/src + cat > $_meson_dir/src/main.c <<EOF +#define _POSIX_C_SOURCE 200809L + +#ifndef VERSION +#define VERSION "undefined" +#endif + +#include <stdlib.h> +#include <stdio.h> + +int main(int argc, char **argv) { + printf("%s version %s\n\n", *argv, VERSION); + + printf("args (len: %d)", argc-1); + for (int i=1; i < argc; ++i) { + printf(" '%s'", argv[i]); + } + printf("\n"); + return 0; +} +EOF + ;; + + ### JAVA ### + java) + _meson_default_options= + _meson_default_files=('pkg/Main.class') + _meson_ignore_extra='**/*.jar' + _meson_output=" +x = jar( + '$_meson_bin', + main_class: 'pkg.Main', + sources: src +) +" + + log "writing Main.java" + mkdir $_meson_dir/pkg + cat > $_meson_dir/pkg/Main.java <<EOF +package pkg + +class Main { + public static void main(String[] args) { + System.out.println("hello world o/"); + } +} +EOF + ;; + + + *) + log "$_meson_lang not supported yet :(" + exit 1 + ;; +esac + +meson_array_expand() { + for o in "${@}"; do + printf "\t'%s',\n" $o + done +} + +log "writing meson.build" +cat > $_meson_dir/meson.build <<EOF +project( + '$_meson_bin', + '$_meson_lang', + version: '0.1.0', + license: 'MIT', + default_options: [ +`meson_array_expand ${_meson_default_options[@]}` + ], +) + +version = '"@0@ (" __DATE__ ")"'.format(meson.project_version()) + +# embed version sourced from git ref +git = find_program('git', native: true, required: false) +if git.found() + git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']) + git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']) + if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0 + version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip()) + endif +endif +add_project_arguments('-DVERSION=@0@'.format(version), language: 'c') + +src = files( +`meson_array_expand ${_meson_default_files[@]}` +) + +$_meson_output + +test('smoke', x) +EOF + +log "intialializing git" +git init -q $_meson_dir + +log "writing .gitignore" +cat > $_meson_dir/.gitignore <<EOF +/build +$_meson_ignore_extra +EOF + +log "writing Makefile" +cat > $_meson_dir/Makefile <<EOF +RT ?= podman +IMAGE ?= docker.io/robertgzr/mesonbuild-$_meson_lang + +all: clean compile + +build/build.ninja: + meson build + +build/$_meson_bin$([[ $_meson_lang -eq java ]] && echo '.jar'): compile +build: build/build.ninja + ninja -C build + +test: build/$_meson_bin$([[ $_meson_lang -eq java ]] && echo '.jar') + ninja -C build test + +cbuild: + \$(RT) run --rm -it -v \$(shell pwd):/src -w /src \$(IMAGE) make + +ctest: + \$(RT) run --rm -it -v \$(shell pwd):/src -w /src \$(IMAGE) make test + +clean: + \$(RM) -r build + +.PHONY: clean build test cbuild ctest +EOF diff --git a/bin/now b/bin/now new file mode 100755 index 0000000..7c312b1 --- /dev/null +++ b/bin/now @@ -0,0 +1,6 @@ +#!/bin/sh + +case "$1" in + -here) exec date +%Y-%m-%dT%H:%M:%SZ%:z ;; + *) exec date -u +%Y-%m-%dT%H:%M:%SZ ;; +esac diff --git a/bin/obsctl b/bin/obsctl new file mode 100755 index 0000000..d964f6b --- /dev/null +++ b/bin/obsctl @@ -0,0 +1,121 @@ +#!/bin/env python3 +# -*- coding: utf-8 -*- + +import argparse +import logging +import sys + +from obswebsocket import obsws, requests + + +def __call(ws, req, cb=None): + r = ws.call(req) + if r.status: + logging.debug('Success calling %s' % req) + if cb: + try: + return cb(r) + except Exception as e: + raise(e) + else: + raise Exception(r) + + +def listActions(*_): + for a in actions: + print(a) + + +def startRecording(ws, _): + __call(ws, requests.StartRecording()) + + +def stopRecording(ws, _): + __call(ws, requests.StopRecording()) + __call(ws, requests.GetRecordingFolder(), + lambda r: print('Saved recording at: %s' % r.getRecFolder())) + + +def startStreaming(ws, _): + __call(ws, requests.StartStreaming()) + + +def stopStreaming(ws, _): + __call(ws, requests.StopStreaming()) + + +def listScenes(ws, _): + def cb(r): + current_scene = r.getCurrentScene() + for scene in r.getScenes(): + scene = scene['name'] + print('%s %s' % (('*' if current_scene == scene else ' '), scene)) + + __call(ws, requests.GetSceneList(), cb) + + +def switchScene(ws, args): + def cb(r): + return list(map(lambda s: s['name'], r.getScenes())) + + scenes = __call(ws, requests.GetSceneList(), cb) + + if len(args) != 1: + raise Exception('need exactly 1 argument') + + if args[0] in scenes: + __call(ws, requests.SetCurrentScene(args[0])) + + +sys.path.append('../') + +actions = { + 'list-actions': listActions, + 'start-recording': startRecording, + 'stop-recording': stopRecording, + 'start-streaming': startStreaming, + 'stop-streaming': stopStreaming, + 'list-scenes': listScenes, + 'switch-scene': switchScene, +} + +if __name__ == '__main__': + try: + app = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + app.add_argument('-d', '--debug', + action='store_true', + help='show debug logs') + app.add_argument('-H', '--host', type=str, + default='localhost', + help='obs-websocket host') + app.add_argument('-P', '--port', type=int, + default=4444, + help='obs-websocket port') + app.add_argument('-p', '--password', type=str, + help='obs-websocket password') + app.add_argument('action', type=str, + choices=actions.keys(), + help='the command to run') + app.add_argument('args', + nargs=argparse.REMAINDER, + help='optional action argument') + args = app.parse_args() + + if args.debug: + logging.basicConfig(level=logging.DEBUG) + + ws = obsws(args.host, args.port, args.password) + ws.connect() + + actions[args.action](ws, args.args) + + ws.disconnect() + exit(0) + + except KeyboardInterrupt: + pass + except Exception as e: + logging.error(e) diff --git a/bin/open b/bin/open new file mode 100755 index 0000000..9a1eb3f --- /dev/null +++ b/bin/open @@ -0,0 +1,59 @@ +#!/bin/bash + +set -m +termmode=0 + +while getopts ":hg" opt; do + case $opt in + g) + termmode=1 + ;; + h|*) + echo "usage: open [-t] FILE" + echo "" + echo " -t Use only Terminal-based applications" + exit 2 + ;; + esac +done +shift $((OPTIND-1)) + +in="$1" + +mime=$(file --mime-type $in | awk -F': ' '{print $2}' ) +ext=$(basename $in | rev | cut -d'.' -f1 | rev) +opener=xdg-open + +fork() { + exec $@ 1>&2 2>/dev/null & +} + +term() { + fork alacritty --title="$1" --command "$@" +} + +case $mime in + inode/directory) [ $termmode -eq 0 ] && vifm "$in" || fork nautilus -w "$in" ;; + # inode/file) fork nautilus -w $in ;; + + image/*) fork sxiv "$in" ;; + video/*) fork mpv --force-widow=yes "$in" ;; + audio/*) fork mpv --force-widow=yes "$in" ;; + + application/pdf) fork zathura "$in" ;; + application/vnd.ms-opentype|application/font-sfnt) + fork gnome-font-viewer "$in" ;; + + # now text... + text/*) + # echo $mime $ext + case $ext in + md|markdown) fork shiba "$(readlink -f "$in")" ;; + desktop) fork gtk-launch "$(basename "$in" | cut -d'.' -f1)" ;; + *) fork term nvim "$in" ;; + esac + ;; + + # everything else... + *) exec $opener "$in" ;; +esac diff --git a/bin/preview b/bin/preview new file mode 100755 index 0000000..41ebfb9 --- /dev/null +++ b/bin/preview @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +REVERSE="\x1b[7m" +RESET="\x1b[m" + +if [ -z "$1" ]; then + echo "usage: $0 FILENAME[:LINENO][:IGNORED]" + exit 1 +fi + +IFS=':' read -r -a INPUT <<< "$1" +FILE=${INPUT[0]} +CENTER=${INPUT[1]} + +if [[ $1 =~ ^[A-Z]:\\ ]]; then + FILE=$FILE:${INPUT[1]} + CENTER=${INPUT[2]} +fi + +if [ ! -r "$FILE" ]; then + echo "File not found ${FILE}" + exit 1 +fi + +if [[ "$(file --dereference --mime "$FILE")" =~ binary ]]; then + echo "$1 is a binary file" + exit 0 +fi + +if [ -z "$CENTER" ]; then + CENTER=0 +fi + +if [ -z "$LINES" ]; then + if [ -r /dev/tty ]; then + LINES=$(stty size < /dev/tty | awk '{print $1}') + else + LINES=40 + fi +fi + +FIRST=$(($CENTER-$LINES/3)) +FIRST=$(($FIRST < 1 ? 1 : $FIRST)) +LAST=$((${FIRST}+${LINES}-1)) + +DEFAULT_COMMAND="bat --style=numbers --color=always {} || highlight -O ansi -l {} || coderay {} || rougify {} || cat {}" +CMD=${FZF_PREVIEW_COMMAND:-$DEFAULT_COMMAND} +CMD=${CMD//{\}/$(printf %q "$FILE")} + +eval "$CMD" 2> /dev/null | awk "NR >= $FIRST && NR <= $LAST { \ + if (NR == $CENTER) \ + { gsub(/\x1b[[0-9;]*m/, \"&$REVERSE\"); printf(\"$REVERSE%s\n$RESET\", \$0); } \ + else printf(\"$RESET%s\n\", \$0); \ + }" diff --git a/bin/qemuu b/bin/qemuu new file mode 100755 index 0000000..3e4548c --- /dev/null +++ b/bin/qemuu @@ -0,0 +1,56 @@ +#!/bin/sh + +Q_ARCH='x86_64' +Q_NAME='qemu' +Q_SSH_PORT='2222' + +USAGE="usage: `basename $0` [options] [--] [qemu options...] +Options: + -A ARCH VM architecture + -p PORT The port on localhost to map to the VM's sshd. [2222] + -d FILE Set disk + -n NAME Set name + -h This ;-) + +A simple wrapper around qemu for running balenaOS images. +See the qemu(1) man page for more details. +" + +Q_ARGS="" + +while getopts ":hvd:A:n:" o; do + case "$o" in + A) + Q_ARCH="$OPTARG" + die "Found \"-$o $Q_ARCH\", not supported yet!" ;; + d) + Q_DISK="$OPTARG" + Q_ARGS="${Q_ARGS} -drive file="${Q_DISK}",media=disk,cache=none,format=raw" ;; + p) + Q_SSH_PORT="$OPTARG" ;; + n) + Q_NAME="$OPTARG" ;; + v) + set -x ;; + h|*) + echo "$USAGE" + exit ;; + esac +done +shift $(($OPTIND-1)) + +case "${Q_ARCH}" in + x86_64|amd64) + qemu-system-x86_64 \ + -name "$Q_NAME" \ + -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \ + -net nic,model=virtio -net user \ + -nic user,hostfwd=tcp::${Q_SSH_PORT}-:22222 \ + -machine type=pc,accel=kvm -smp 4 -cpu host \ + ${Q_ARGS} \ + "$@" + ;; + *) die "Unsupported arch" ;; +esac + +exit $? diff --git a/bin/randgen b/bin/randgen new file mode 100755 index 0000000..77bca83 --- /dev/null +++ b/bin/randgen @@ -0,0 +1,8 @@ +#!/bin/zsh + +N=${1:-10} + +for d in {0..$N}; do + printf '%s' ${RANDOM:0:1} +done +printf '\n' diff --git a/bin/semver b/bin/semver new file mode 100755 index 0000000..e2b51e7 --- /dev/null +++ b/bin/semver @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +# src: +# https://git.sr.ht/~sircmpwn/dotfiles/blob/f9e7e88a72c2b87e5ab823205cb3a3aa8f3babd0/bin/semver +# usage: semver [patch|minor|major|<new_version>] +# desc: will tag a new version either based on last tag or <new_version> +# +# patch(robertgzr) +# allow simply tagging a new non-annotated release + +import os +import subprocess +import sys +import tempfile + +if subprocess.run(["git", "branch", "--show-current"], + stdout=subprocess.PIPE).stdout.decode().strip() != "master": + print("WARNING! Not on the master branch.") + +subprocess.run(["git", "pull", "--rebase"]) + +if sys.argv[1] == "-t": + sys.argv = [sys.argv[0]] + sys.argv[2:] + simple_tag = True + describe_args = "--tag" +else: + describe_args = "--abbrev=0" + +p = subprocess.run(["git", "describe", describe_args], stdout=subprocess.PIPE) +describe = p.stdout.decode().strip() +old_version = describe.split("-")[0].split(".") +if len(old_version) == 2: + [major, minor] = old_version + [major, minor] = map(int, [major, minor]) + patch = 0 +else: + [major, minor, patch] = old_version + [major, minor, patch] = map(int, [major, minor, patch]) + +p = subprocess.run(["git", "shortlog", "--no-merges", f"{describe}..HEAD"], + stdout=subprocess.PIPE) +shortlog = p.stdout.decode() + +new_version = None + +if sys.argv[1] == "patch": + patch += 1 +elif sys.argv[1] == "minor": + minor += 1 + patch = 0 +elif sys.argv[1] == "major": + major += 1 + minor = patch = 0 +else: + new_version = sys.argv[1] + +if new_version is None: + if len(old_version) == 2 and patch == 0: + new_version = f"{major}.{minor}" + else: + new_version = f"{major}.{minor}.{patch}" + +p = None +if os.path.exists("contrib/_incr_version"): + p = subprocess.run(["contrib/_incr_version", describe, new_version]) +elif os.path.exists(".git/_incr_version"): + print("Warning: _incr_version found at legacy path") + p = subprocess.run([".git/_incr_version", describe, new_version]) +else: + print("Warning: no _incr_version script. " + + "Does this project have any specific release requirements?") + +if p and p.returncode != 0: + print("Error: _incr_version returned nonzero exit code") + sys.exit(1) + +if simple_tag: + subprocess.run(["git", "tag", new_version]) + +else: + with tempfile.NamedTemporaryFile() as f: + basename = os.path.basename(os.getcwd()) + f.write(f"{basename} {new_version}\n\n".encode()) + f.write(shortlog.encode()) + f.flush() + subprocess.run(["git", "tag", "-e", "-F", f.name, "-a", new_version]) + +print(new_version) diff --git a/bin/soundswitch_dmenu b/bin/soundswitch_dmenu new file mode 100755 index 0000000..f4a6338 --- /dev/null +++ b/bin/soundswitch_dmenu @@ -0,0 +1,133 @@ +#!/bin/sh +# +# This script changes the pulseaudio sink for the currently focused application +# +# If it is run with $1 = global the default sink will be switched instead + +set -eo pipefail + +rofi() { + # exec rofi -dmenu -p pactl -l 5 -i "$@" + exec wofi --dmenu -k /dev/null --prompt pactl "$@" +} +bail() { + exec echo "$@" | wofi -p pactrl-error + exit 1 +} + +list_sinks() { + pacmd list-sinks | tr '\n' '\r' | perl -pe 's/[^\r]*index: ([0-9]+).+?name: <([^\r]+)>.+?device.description = "([^\r]+)"\r.+?(?=index:|$)/\1: \3 \t<\2>\r/g' | tr '\r' '\n' | tail -n +2 +} +parse_sink() { + perl -pe 's/.*<([^\r]+)>/\1/g' +} + +# usage: move_sink_inputs_to <target-sink> <sink-input-ids> +move_sink_inputs_to() { + target_sink_id="${1}" + shift + for sink_input_id in "${@}"; do + # notify-send "pactl-debug" "pacmd move-sink-input ${sink_input_id} ${target_sink_id}" + pacmd move-sink-input ${sink_input_id} ${target_sink_id} + done +} + +list_active_sink_input_ids() { + pacmd list-sink-inputs | tr '\n' '\r' | perl -pe 's/[^\r]*index: ([0-9]+).+?application\.process\.binary = "([^\r]+)"\r.+?(?=index:|$)/\2: \1\r/g' | tr '\r' '\n' | tail -n +2 +} +parse_active_sink_input_id() { + perl -pe 's/.*:([^\r]+)/\1/g' +} + +list_cards() { + pacmd list-cards | tr '\n' '\r' | perl -pe 's/[^\r]*index: ([0-9]+).+?device.description = \"([^\r]+)\".+?active.profile: <([^\r]+)>.+?(?=index|$)/\1: \2\tProfile: <\3>\r/g' | tr '\r' '\n' | tail -n +2 +} + +list_card_profiles() { + idx="${1}" + pacmd list-cards | tr '\n' '\r' | perl -pe "s/.+index: $idx\\r.+?[^\\r]*profiles:\\r(.+?)[^\\r]*active.+?$/\\1/g;" -e 's/\t\t//g;' -e 's/\([^\r]+\)\r/\r/g;' -e 's/([^\r]+):[\s]+([^\r]+)[\s]+(\r|$)/\2 <\1>\r/g;' | tr '\r' '\n' | tail -n +2 +} +parse_profile() { + perl -pe 's/^.+<(.+)>$/\1/g' +} + +get_focused_command() { + focused_pid=`swaymsg -t get_tree | jq '.. | select(.focused? == true) | .pid'` + ps -o comm= $focused_pid +} + +# switch default global sink +_default() { + target_sink=`echo "${sel}" | parse_sink` + pacmd set-default-sink $target_sink + notify-send --expire-time=2000 "New default sink" "<b>${target_sink}</b>" + move_sink_inputs_to $target_sink `pactl list sink-inputs short | cut -f 1 | tr '\n' ' '` +} + +# switch output sink of focused application +_focused() { + focused_app=`get_focused_command` + focused_app_input_sink_id=`list_active_sink_input_ids | grep "${focused_app}" | parse_active_sink_input_id` + target_sink=`list_sinks | rofi | parse_sink` + + notify-send --expire-time=2000 "Switch sink" "Switch <b>${focused_app}</b> to <i>${target_sink}</i>" + move_sink_inputs_to "${target_sink}" "${focused_app_input_sink_id}" +} + +# switch output sink of application +_app() { + ret=`list_active_sink_input_ids | rofi` + focused_app_input_sink_id=`echo $ret | parse_active_sink_input_id` + focused_app=`echo $ret | perl -pe 's/^(.+):.+$/\1/g'` + + target_sink=`list_sinks | rofi | parse_sink` + + notify-send --expire-time=2000 "Switch sink" "Switch <b>${focused_app}</b> to <i>${target_sink}</i>" + move_sink_inputs_to "${target_sink}" "${focused_app_input_sink_id}" +} + +_switch_profile() { + card=`list_cards | rofi` + card_id=`echo $card | perl -pe 's/([0-9]+):.+$/\1/g'` + card_name=`echo $card | perl -pe 's/^.+: (.+?)[\s]+Profile.+$/\1/g'` + new_profile=`list_card_profiles $card_id | rofi | parse_profile` + + notify-send -t 2000 "Change card profile" "Switch <b>$card_name</b> to <i>$new_profile</i>" + # notify-send "pactl-debug" "pacmd set-card-profile $card_id $new_profile" + pacmd set-card-profile $card_id $new_profile +} + +ACTIONS=( + "Switch profile" + "Change app output sink" +) +list_actions() { + for a in "${ACTIONS[@]}"; do echo "$a"; done +} + +_global() { + sel=`{ list_sinks; echo ; list_actions; } | rofi` + if [[ -z $sel ]]; then + return + fi + case "${sel}" in + ${ACTIONS[0]}) + _switch_profile + ;; + ${ACTIONS[1]}) + _app + ;; + *) + _default + ;; + esac +} + +case "${1}" in + global) + _global + ;; + *) + _focused + ;; +esac diff --git a/bin/streamon b/bin/streamon new file mode 100755 index 0000000..a652b77 --- /dev/null +++ b/bin/streamon @@ -0,0 +1,7 @@ +#!/bin/sh + +set -ex + +sway exec "wshowkeys -a bottom -a left -F 'Iosevka R 26' -t 1" + +obsctl start-streaming diff --git a/bin/sway-run b/bin/sway-run new file mode 100755 index 0000000..be60ae4 --- /dev/null +++ b/bin/sway-run @@ -0,0 +1,47 @@ +#!/bin/sh + +set -ex + +if [ "$XDG_RUNTIME_DIR" == "" ]; then + export XDG_RUNTIME_DIR=/tmp +fi + +LOCKED=0 +SWAY_BIN=/usr/bin/sway +SWAY_ARGS= + +while getopts ":dcDL" o; do + case $o in + d) SWAY_ARGS="$SWAY_ARGS --debug" ;; + D) + export XDG_RUNTIME_DIR=/tmp/sway-run-debug + [ ! -d "$XDG_RUNTIME_DIR" ] && mkdir -p "$XDG_RUNTIME_DIR" + ;; + c) SWAY_ARGS="$SWAY_ARGS --config $XDG_CONFIG_HOME/sway/simple.conf" ;; + L) LOCKED=1 ;; + *) echo "unknown flag: $o" 1>&2 + esac +done +shift $((OPTIND-1)) + +if [ $LOCKED -eq 1 ]; then + sh -c 'sleep 1; $HOME/.dotfiles/bin/lock' & disown + exec "$0" $@ +fi + +case $1 in + locked) shift; exec $0 -L ;; + git) shift; + SWAY_SRC="$HOME/devel/upstream/github.com/swaywm/sway" + SWAY_BIN="$SWAY_SRC/build/sway/sway"; + export PATH="$SWAY_SRC/build/sway:$PATH" + export LD_LIBRARY_PATH="$SWAY_SRC/build/subprojects/wlroots:$LD_LIBRARY_PATH" + ;; + system) shift; exec $0 ;; + clean) shift; exec $0 -c ;; +esac + +TS="$(date -u --rfc-3339=seconds | tr ' ' 'T')" +LOG=/var/log/sway/"$TS".log + +exec $SWAY_BIN -Ddamage=rerender $SWAY_ARGS $@ 1>&2 2>"$LOG" diff --git a/bin/swq b/bin/swq new file mode 100755 index 0000000..0336f64 --- /dev/null +++ b/bin/swq @@ -0,0 +1,139 @@ +#!/usr/bin/env bash + +swtree() +{ + swaymsg -t get_tree +} + +has_appid() +{ + swtree | jq "[ .. | select(.app_id?==\"$1\") // select(.window_properties?.class==\"$1\") // select(.window_properties?.instance==\"$1\") ]" +} + +has_pid() +{ + q='' + IFS=$' '; for pid in $1; do + if [[ -n $q ]]; then + q=".pid?==$pid or $q" + else + q=".pid?==$pid" + fi + done + swtree | jq "[ .. | select($q) ]" +} + +has_exe() +{ + pid_of_win="$(pgrep -x "$1" -d' ')" + if [[ -z "$pid_of_win" ]]; then + echo "process not found" >&2 + exit 1 + fi + + ppid_of_proc="$(ps -q "$(echo "$pid_of_win" | tr ' ' ',')" -o ppid= |tr -d ' '| tr '\n' ','| sed 's/,$//')" + if [[ -z "$ppid_of_proc" ]]; then + echo "parent process not found" >&2 + fi + IFS=$'\n'; for comm in $(ps -q "$ppid_of_proc" -o pid=,comm=); do + if [[ "$comm" =~ ^.*sh$ ]]; then + # parent process is a shell + # need to get parent process of shell (the terminal emulator) + pid_of_win="$pid_of_win $(ps -p "$(echo "$comm" | awk '{print $1}')" -o ppid= | tr -d ' ')" + fi + done + + if [[ -z "$pid_of_win" ]]; then + echo "process not found" >&2 + exit 1 + fi + has_pid "$pid_of_win" +} + +latest() +{ + type="$1" + swtree | jq "[ .. | select(.type?==\"$type\") ] | max_by(.id)" +} + +list() +{ + swtree | jq "[ .. | select(.type?==\"$1\") | .nodes=[\"...\"] ]" +} + +moveto() +{ + if [ ! -t 0 ]; then + read -r -i -e conId + sway [con_id="$conId"] move to workspace "$1" + else + sway [app_id="$1"] move to workspace "$2" + fi +} + +# meta commands + +_has() +{ + case "$1" in + appid) has_appid "$2" ;; + pid) has_pid "$2";; + exe) has_exe "$2";; + *) + printf 'usage: swq has COMMAND <args>\n' + printf '\nCOMMANDS:\n' + printf ' appid: returns the json of the window filtered by app_id/class\n' + printf ' pid: returns the json of the window filtered by pid\n' + printf ' exe: returns the json of the window filtered by process name\n' + printf '\n' + esac +} +_latest() +{ + [ "$1" == "ws" ] && 1="workspace" + latest "$1" +} +_list() +{ + [ -n "$1" ] || { echo "usage: swq list <con|workspace>"; exit 1; } + [ "$1" == "ws" ] && _list workspace + list "$1" +} +_current() +{ + case "$1" in + window) _current con ;; + con) swtree | jq '.. | select(.type?=="con" and .focused?==true)' ;; + + ws) _current workspace ;; + workspace) swtree | jq '.. | select(.type?=="workspace") | . as $parent | .nodes[]? | if .focused then $parent else empty end' ;; + output) $0 current workspace | jq -r '.output' ;; + *) + printf 'usage: swq current <con|workspace|output>\n\n' ;; + esac +} + +case "$1" in + has) shift; _has $@ ;; + latest) shift; _latest $@ ;; + list|ls) shift; _list $@ ;; + current|cur) shift; _current $@ ;; + + focus-latest-con) sway [con_id="$(latest con | jq -r '.id')"] focus ;; + moveto) shift; moveto $@ ;; + + *) + printf 'usage: swq COMMAND\n' + printf '\n' + printf 'QUERY COMMANDS:\n' + printf ' has: get object by criteria\n' + printf ' latest: get newest objects\n' + printf ' list: list objects\n' + printf ' current: get focused objects\n' + + printf '\n' + printf 'ACTION COMMANDS:\n' + printf ' focus-latest-con\n' + printf ' moveto <appid> <ws>: move container with <appid> to <ws>\n' + printf '\n' +esac diff --git a/bin/termify b/bin/termify new file mode 100755 index 0000000..4778470 --- /dev/null +++ b/bin/termify @@ -0,0 +1,10 @@ +#!/bin/bash + +arg0=$(basename $1 || echo $1) + +case "$TERM" in + alacritty) args="--title='$arg0' --class='$arg0' --command" ;; + *) args="" ;; +esac + +exec $TERM $args $@ & diff --git a/bin/todo b/bin/todo new file mode 100755 index 0000000..1fe3c62 --- /dev/null +++ b/bin/todo @@ -0,0 +1,128 @@ +#!/bin/bash + +_todo_files=(".todo.md" ".org.md", "TODO") +_todo_file="" + +has_todo_file() { + cwd=`pwd` + for f in ${_todo_files[@]}; do + [[ -f "$cwd/$f" ]] && _todo_file="$cwd/$f" && return 0 + done + return 1 +} + +print_org() { + awk ' +function filename() { + n = split(FILENAME, a, "/") + return a[n] +} +function res() { + return "\033[0;10m" +} +function bold(s) { + return "" s res() +} +function red(s) { + "tput setaf 1" | getline redc; + return redc s res() +} +function green(s) { + "tput setaf 2" | getline greenc; + return greenc s res() +} +function blue(s) { + "tput setaf 5" | getline bluec; + return bluec s res() +} +function magenta(s) { + "tput setaf 191" | getline magc; + return magc s res() + } +{ + if ($1 ~ /^#+/) + block=$0 + + if ($1 ~ /^.*(-|*)/) { + // support TODO files + if (filename() == "TODO") { + $0 = gensub(/(^[\w]*(-|*))/, "\\1 TODO", "1", $0) + } + + match($0, /\s(TODO|WIP|WAITING|DONE|\?\?\?)\s/, arr) + if (arr[1] != "") { + tasks[block][arr[1]][i++]=$0 + } + } +} +END { + for (b in tasks) { + print b + for (status in tasks[b]) { + for (t in tasks[b][status]) { + txt = tasks[b][status][t] + txt = gensub(/(TODO)/, red("\\1"), "1", txt) + txt = gensub(/(WIP)/, blue("\\1"), "1", txt) + txt = gensub(/(DONE)/, green("\\1"), "1", txt) + txt = gensub(/(\?\?\?)/, bold(magenta("\\1")), "1", txt) + print txt + } + } + printf "\n" + } +}' $1 +} + +print_shell_hook() { + case $1 in + zsh) + echo "_orgdown_hook() {$0;} +typeset -ag precmd_functions; +if [[ -z \${precmd_functions[(r)_orgdown_hook]} ]]; then + precmd_functions+=_orgdown_hook; +fi" + ;; + cd) + echo "cd() { builtin cd \"\$@\" && $0; }" + ;; + -h|--help|*) + echo "usage: todo hook <integration>" + echo "" + echo "supported integrations:" + echo " - zsh: run todo as a precmd hook" + echo " (this can get quite annoying when you're in a direcory that has a todo file)" + echo " - cd: run todo as a post-cd hook" + echo "" + exit 1 + ;; + esac +} + +case "$1" in + hook) + shift + print_shell_hook "$1" + ;; + -h|--help) + echo "usage: todo [-r] [hook <integration>]" + echo "A quite stupid todo/agenda script that reminds you of things you need to do" + echo "" + echo " -r, --recursive walk the file tree, searching for todos" + echo " <integration> shell integration, you should eval this" + echo "" + exit 0 + ;; + -r|--recursive|-R) + while read FILE; do + hr "todo [$FILE]" + print_org $FILE; + done < <(rg --files ${_todo_files[@]/#/-g }) + ;; + *) + [ -f "$1" ] && { print_org $1; exit 0; } + has_todo_file && { + hr "todo" + print_org $_todo_file + } + ;; +esac diff --git a/bin/trash b/bin/trash new file mode 100755 index 0000000..5b810c1 --- /dev/null +++ b/bin/trash @@ -0,0 +1,53 @@ +#!/bin/sh + +set -e + +TRASH="$HOME/.trash" +if [ ! -d "$TRASH" ]; then + mkdir -p "$TRASH" +fi + +empty_trash() { + if [ ! -d "$TRASH" ] || [ -z "$(ls "$TRASH")" ]; then + exit 0 + fi + echo " +++ trash +++" + ls "$TRASH" + printf " +++ take it out? [yN] "; read -r yn + if [ "$yn" = "y" ]; then + printf ' +++ removing %s/* ' "$TRASH" + rm -rf "${TRASH:?}/*" + echo 'done.' + fi +} + +install_auto_empty() { + case "$SHELL" in + *zsh) logoutfile="$ZDOTDIR/.zlogout" ;; + *bash) logoutfile="$HOME/.bash_logout" ;; + *) + echo "unknown shell $SHELL" 1>&2; exit 1 ;; + esac + cat > "$logoutfile" <<EOF +# auto-empty-trash routine of $0 +yes | $0 --empty +EOF +} + +case "$1" in + -e|--empty) + empty_trash + exit 0 ;; + -i|--install) + install_auto_empty + exit 0 ;; + -h|--help) + echo "usage: trash [-e|--empty] FILE..." + exit 1 ;; +esac + +# main +for f in "$@"; do + echo "+ $(basename "$f")" + mv "$f" "$TRASH/" +done diff --git a/bin/url-launcher b/bin/url-launcher new file mode 100755 index 0000000..4bb682e --- /dev/null +++ b/bin/url-launcher @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e +wl-copy "$@" +notify-send --urgency=low "copied url" "$@" diff --git a/bin/usb2sys b/bin/usb2sys new file mode 100755 index 0000000..d810431 --- /dev/null +++ b/bin/usb2sys @@ -0,0 +1,36 @@ +#!/bin/bash +# +# usb2sys - find lsusb device in /sys file system +# +# https://superuser.com/a/1253083 +# + +die() +{ + echo "$@" + exit 1 +} + +[[ $# -lt 1 ]] && die "need vendor and product ids (from lsusb) as dddd:dddd" + +vendor=${1%:*} +product=${1##*:} + +sys=/sys/bus/usb/devices/ +cd $sys + +for d in *; do + path=$sys/$d + if [ -f $path/idProduct ]; then + prod=$( cat $path/idProduct ) + vend=$( cat $path/idVendor ) + + if [ $prod = $product -a $vend = $vendor ]; then + echo prod = $prod + echo vend = $vend + echo /sys device is $path + cat $path/power/wakeup + echo "" + fi + fi +done diff --git a/bin/wsudo b/bin/wsudo new file mode 100755 index 0000000..1ac1e33 --- /dev/null +++ b/bin/wsudo @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# +# Enable root access to x-windows system. +# +# Motivation: Trying to run a graphical application as root via su, sudo in a +# Wayland session (e.g. GParted or Gedit), will fail. Apps which use polkit to +# request administrator permissions for just certain operations and only when +# needed are not affected (they are not started as root right away). +# [1] https://bugzilla.redhat.com/show_bug.cgi?id=1274451 +# +# Based on a Reddit comment. +# [2] https://www.reddit.com/r/Fedora/comments/5eb633/solution_running_graphical_app_with_sudo_in/ + +usage() { + printf "usage: wsudo [-d] [command]\n" +} + +# make sure there is an argument +if (( $# == 0 )); then + echo "error: Illegal number of parameters." >&2 + exit 1 +fi + +if ! type -p "$1" &>/dev/null; then + echo "$1 not found. Aborting." >&2 + exit 1 +fi + +for cmd in sudo xhost; do + if ! type -P $cmd &>/dev/null; then + echo "$cmd it's not installed. Aborting." >&2 + exit 1 + fi +done + +set -x + +xhost +SI:localuser:root +SUDO_ASKPASS="`command -v askpass`" sudo -A "${@}" +#disable root access after application terminates +xhost -SI:localuser:root +#print access status to allow verification that root access was removed +xhost |